diff --git a/.eslintrc.js b/.eslintrc.js index 037eb10eb06..b3e5e39dc9f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,7 +12,7 @@ module.exports = { mocha: true, es2024: true, }, - plugins: ['@typescript-eslint', 'unicorn', 'header', 'security-node', 'aws-toolkits'], + plugins: ['@typescript-eslint', '@stylistic', 'unicorn', 'header', 'security-node', 'aws-toolkits'], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended', @@ -113,6 +113,20 @@ module.exports = { 'no-constant-condition': ['error', { checkLoops: false }], 'no-empty': 'off', + // https://eslint.style/rules/default/spaced-comment + // Require space after // comment. + '@stylistic/spaced-comment': [ + 'error', + 'always', + { + block: { + markers: ['!'], // Allow the /*!…*/ license header. + // exceptions: ['*'], + // balanced: true + }, + }, + ], + // Rules from https://github.com/sindresorhus/eslint-plugin-unicorn // TODO: 'unicorn/no-useless-promise-resolve-reject': 'error', // TODO: 'unicorn/prefer-at': 'error', @@ -162,7 +176,8 @@ module.exports = { 'aws-toolkits/no-incorrect-once-usage': 'error', 'aws-toolkits/no-string-exec-for-child-process': 'error', 'aws-toolkits/no-console-log': 'error', - + 'aws-toolkits/no-json-stringify-in-log': 'error', + 'aws-toolkits/no-printf-mismatch': 'error', 'no-restricted-imports': [ 'error', { @@ -173,13 +188,24 @@ module.exports = { "Avoid importing from the core lib's dist/ folders; please use directly from the core lib defined exports.", }, ], + // The following will place an error on the `fs-extra` import since we do not want it to be used for browser compatibility reasons. + paths: [ + { + name: 'fs-extra', + message: + 'Avoid fs-extra, use shared/fs/fs.ts. Notify the Toolkit team if your required functionality is not available.', + }, + { + name: 'fs', + message: 'Avoid node:fs and use shared/fs/fs.ts when possible.', + }, + { + name: 'child_process', + message: + 'Avoid child_process, use ChildProcess from `shared/utilities/processUtils.ts` instead.', + }, + ], }, - // The following will place an error on the `fs-extra` import since we do not want it to be used for browser compatibility reasons. - // { - // name: 'fs-extra', - // message: - // 'Avoid fs-extra, use shared/fs/fs.ts. Notify the Toolkit team if your required functionality is not available.', - // }, ], }, } diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9d6c9e267f7..c32f432ddac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,6 @@ * @aws/aws-ides-team packages/core/src/codewhisperer/ @aws/codewhisperer-team packages/core/src/amazonqFeatureDev/ @aws/earlybird -packages/core/src/codewhispererChat/ @aws/dexp -packages/core/src/amazonq/ @aws/dexp +packages/core/src/codewhispererChat/ @aws/flare +packages/core/src/amazonq/ @aws/flare packages/core/src/awsService/accessanalyzer/ @aws/access-analyzer diff --git a/.github/ISSUE_TEMPLATE/unreliable_test_report.md b/.github/ISSUE_TEMPLATE/unreliable_test_report.md new file mode 100644 index 00000000000..a1981703b69 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/unreliable_test_report.md @@ -0,0 +1,17 @@ +--- +name: unreliable test +about: Help us monitor our test suite by reporting flaky tests. +labels: tests-ci-cd +--- + +## Test Details + +- Name of test: +- OS of failure: +- VSCode version of failure (minimum/insider/stable): +- Link to failing run: +- Link to failing test: + +## Log of Test Failure + +## Additional Information diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7889305394b..3b891b24003 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,6 +6,6 @@ --- - - -License: I confirm that my contribution is made under the terms of the Apache 2.0 license. +- Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. +- Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). +- License: I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/.github/workflows/jscpd.json b/.github/workflows/jscpd.json new file mode 100644 index 00000000000..dd5faa12b30 --- /dev/null +++ b/.github/workflows/jscpd.json @@ -0,0 +1,9 @@ +{ + "pattern": "packages/**/*.ts", + "ignore": ["**node_modules**", "**dist**", "**/scripts/**"], + "gitignore": true, + "threshold": 3.0, + "minLines": 10, + "output": "./", + "reporters": ["json"] +} diff --git a/.github/workflows/lintbranch.js b/.github/workflows/lintbranch.js new file mode 100644 index 00000000000..05fc677dac5 --- /dev/null +++ b/.github/workflows/lintbranch.js @@ -0,0 +1,67 @@ +// Check that branch name conforms to GitHub naming convention: +// https://docs.github.com/en/get-started/using-git/dealing-with-special-characters-in-branch-and-tag-names#naming-branches-and-tags + +// To run self-tests, +// node lintbranch.js test +// TODO: deduplicate code from lintbranch.js and lintcommit.js. + +function isValid(branchName) { + const branchNameRegex = /^[a-zA-Z][a-zA-Z0-9._/-]*$/ + + return branchNameRegex.test(branchName) +} + +function run(branchName) { + if (isValid(branchName)) { + console.log(`Branch name "${branchName}" is valid.`) + process.exit(0) + } else { + const helpUrl = + 'https://docs.github.com/en/get-started/using-git/dealing-with-special-characters-in-branch-and-tag-names#naming-branches-and-tags' + console.log(`Branch name "${branchName}" is invalid see ${helpUrl} for more information.`) + process.exit(1) + } +} + +function _test() { + const tests = { + 'feature/branch-name': true, + feature_123: true, + 'my-branch': true, + '123invalid-start': false, + '!invalid@start': false, + '': false, + 'another/valid-name134': true, + 'feature/123";id;{echo,Y2F0IC9ldGMvcGFzc3dk}|{base64,-d}|{bash,-i};#': false, + } + + let passed = 0 + let failed = 0 + + for (const [branchName, expected] of Object.entries(tests)) { + const result = isValid(branchName) + if (result === expected) { + console.log(`✅ Test passed for "${branchName}"`) + passed++ + } else { + console.log(`❌ Test failed for "${branchName}" (expected "${expected}", got "${result}")`) + failed++ + } + } + + console.log(`\n${passed} tests passed, ${failed} tests failed`) +} + +function main() { + const mode = process.argv[2] + + if (mode === 'test') { + _test() + } else if (mode === 'run') { + run(process.argv[3]) + } else { + throw new Error(`Unknown mode: ${mode}`) + } +} + +main() diff --git a/.github/workflows/lintcommit.js b/.github/workflows/lintcommit.js index 9e3f8b40406..4f329223eef 100644 --- a/.github/workflows/lintcommit.js +++ b/.github/workflows/lintcommit.js @@ -130,6 +130,7 @@ Invalid pull request title: \`${title}\` * scope: lowercase, <30 chars * subject: must be <100 chars * documentation: https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#pull-request-title +* Hint: *close and re-open the PR* to re-trigger CI (after fixing the PR title). ` : `Pull request title matches the [expected format](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#pull-request-title).` diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index f39a9a4b314..1c5299c8a2e 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -7,15 +7,10 @@ on: push: branches: [master, staging] pull_request: - branches: [master, feature/*, staging] - # Default = opened + synchronize + reopened. - # We also want "edited" so that lint-commits runs when PR title is updated. + # By default, CI will trigger on opened/synchronize/reopened event types. # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request - types: - - edited - - opened - - reopened - - synchronize + # Note: To re-run `lint-commits` after fixing the PR title, close-and-reopen the PR. + branches: [master, feature/*, staging] # Cancel old jobs when a pull request is updated. concurrency: @@ -24,6 +19,7 @@ concurrency: jobs: lint-commits: + # Note: To re-run `lint-commits` after fixing the PR title, close-and-reopen the PR. runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -32,6 +28,12 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '20' + - name: Validate Branch name + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref != ''}} + env: + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + run: | + node "$GITHUB_WORKSPACE/.github/workflows/lintbranch.js" run "$BRANCH_NAME" - name: Check PR title run: | node "$GITHUB_WORKSPACE/.github/workflows/lintcommit.js" @@ -41,7 +43,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [16.x] + node-version: [18.x] vscode-version: [stable] env: NODE_OPTIONS: '--max-old-space-size=8192' @@ -55,6 +57,87 @@ jobs: - run: npm run testCompile - run: npm run lint + jscpd: + needs: lint-commits + if: ${{ github.event_name == 'pull_request'}} + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x] + env: + NODE_OPTIONS: '--max-old-space-size=8192' + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Fetch fork upstream + env: + REPO_NAME: ${{ github.event.pull_request.head.repo.full_name }} + run: | + git remote add forkUpstream https://github.com/$REPO_NAME # URL of the fork + git fetch forkUpstream # Fetch fork + + - name: Compute git diff + env: + CURRENT_BRANCH: ${{ github.head_ref }} + TARGET_BRANCH: ${{ github.event.pull_request.base.ref }} + run: git diff --name-only origin/$TARGET_BRANCH forkUpstream/$CURRENT_BRANCH > diff_output.txt + + - run: npm install -g jscpd + + - run: jscpd --config "$GITHUB_WORKSPACE/.github/workflows/jscpd.json" + + - if: always() + uses: actions/upload-artifact@v4 + with: + name: unfiltered-jscpd-report + path: ./jscpd-report.json + + - name: Filter jscpd report for changed files + run: | + if [ ! -f ./jscpd-report.json ]; then + echo "jscpd-report.json not found" + exit 1 + fi + echo "Filtering jscpd report for changed files..." + CHANGED_FILES=$(jq -R -s -c 'split("\n")[:-1]' diff_output.txt) + echo "Changed files: $CHANGED_FILES" + jq --argjson changed_files "$CHANGED_FILES" ' + .duplicates | map(select( + (.firstFile?.name as $fname | $changed_files | any(. == $fname)) or + (.secondFile?.name as $sname | $changed_files | any(. == $sname)) + )) + ' ./jscpd-report.json > filtered-jscpd-report.json + cat filtered-jscpd-report.json + + - name: Check for duplicates + run: | + if [ $(wc -l < ./filtered-jscpd-report.json) -gt 1 ]; then + echo "filtered_report_exists=true" >> $GITHUB_ENV + else + echo "filtered_report_exists=false" >> $GITHUB_ENV + fi + - name: upload filtered report (if applicable) + if: env.filtered_report_exists == 'true' + uses: actions/upload-artifact@v4 + with: + name: filtered-jscpd-report + path: ./filtered-jscpd-report.json + + - name: Fail and log found duplicates. + if: env.filtered_report_exists == 'true' + run: | + cat ./filtered-jscpd-report.json + echo "Duplications found, failing the check." + exit 1 + macos: needs: lint-commits name: test macOS @@ -62,7 +145,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [16.x] + node-version: [18.x] vscode-version: [minimum, stable, insiders] env: VSCODE_TEST_VERSION: ${{ matrix.vscode-version }} @@ -80,23 +163,12 @@ jobs: uses: coactions/setup-xvfb@v1 with: run: npm test - - name: Code coverage (Core) - env: - # Unset NODE_OPTIONS because of https://github.com/codecov/uploader/issues/475 - NODE_OPTIONS: '' - if: ${{ github.repository == 'aws/aws-toolkit-vscode' && github.ref == 'master' }} - uses: codecov/codecov-action@v4 - with: - flags: macos-core-unittests - verbose: true - file: ./coverage/core/lcov.info - token: ${{ secrets.CODECOV_TOKEN }} - name: Code coverage (Toolkit) env: # Unset NODE_OPTIONS because of https://github.com/codecov/uploader/issues/475 NODE_OPTIONS: '' - if: ${{ github.repository == 'aws/aws-toolkit-vscode' && github.ref == 'master' }} - uses: codecov/codecov-action@v4 + if: ${{ github.repository == 'aws/aws-toolkit-vscode' && github.event_name == 'pull_request' && github.base_ref == 'master' }} + uses: codecov/codecov-action@v5 with: flags: macos-toolkit-unittests verbose: true @@ -106,24 +178,13 @@ jobs: env: # Unset NODE_OPTIONS because of https://github.com/codecov/uploader/issues/475 NODE_OPTIONS: '' - if: ${{ github.repository == 'aws/aws-toolkit-vscode' && github.ref == 'master' }} - uses: codecov/codecov-action@v4 + if: ${{ github.repository == 'aws/aws-toolkit-vscode' && github.event_name == 'pull_request' && github.base_ref == 'master' }} + uses: codecov/codecov-action@v5 with: flags: macos-amazonq-unittests verbose: true file: ./coverage/amazonq/lcov.info token: ${{ secrets.CODECOV_TOKEN }} - - name: Code coverage (CodeWhisperer) - env: - # Unset NODE_OPTIONS because of https://github.com/codecov/uploader/issues/475 - NODE_OPTIONS: '' - if: ${{ github.repository == 'aws/aws-toolkit-vscode' && github.ref == 'master' }} - uses: codecov/codecov-action@v4 - with: - flags: codewhisperer - verbose: true - file: ./coverage/core/lcov.info - token: ${{ secrets.CODECOV_TOKEN }} web: needs: lint-commits @@ -132,7 +193,7 @@ jobs: strategy: fail-fast: true matrix: - node-version: [16.x] + node-version: [18.x] vscode-version: [stable, insiders] env: VSCODE_TEST_VERSION: ${{ matrix.vscode-version }} @@ -158,7 +219,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [16.x] + node-version: [18.x] vscode-version: [stable, insiders] env: VSCODE_TEST_VERSION: ${{ matrix.vscode-version }} @@ -176,8 +237,8 @@ jobs: env: # Unset NODE_OPTIONS because of https://github.com/codecov/uploader/issues/475 NODE_OPTIONS: '' - if: ${{ github.repository == 'aws/aws-toolkit-vscode' && github.ref == 'master' }} - uses: codecov/codecov-action@v4 + if: ${{ github.repository == 'aws/aws-toolkit-vscode' && github.event_name == 'pull_request' && github.base_ref == 'master' }} + uses: codecov/codecov-action@v5 with: flags: windows-unittests verbose: true diff --git a/.github/workflows/notification.yml b/.github/workflows/notification.yml index 109bbabdfb3..17f0327e5c1 100644 --- a/.github/workflows/notification.yml +++ b/.github/workflows/notification.yml @@ -6,15 +6,10 @@ name: Notifications on: # `pull_request_target` (as opposed to `pull_request`) gives permissions to comment on PRs. pull_request_target: - branches: [master, feature/*, staging] - # Default = opened + synchronize + reopened. - # We also want "edited" so that changelog notifications runs when PR title is updated. + # By default, CI will trigger on opened/synchronize/reopened event types. # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request - types: - - edited - - opened - - reopened - - synchronize + # Note: To re-run `lint-commits` after fixing the PR title, close-and-reopen the PR. + branches: [master, feature/*, staging] # Cancel old jobs when a pull request is updated. concurrency: @@ -37,7 +32,7 @@ jobs: if: github.event_name == 'pull_request_target' with: node-version: '20' - - name: Check for tests + - name: Comment about contribution guidelines uses: actions/github-script@v7 if: github.event_name == 'pull_request_target' with: diff --git a/.github/workflows/notify.js b/.github/workflows/notify.js index 9eab77d6318..835acddf706 100644 --- a/.github/workflows/notify.js +++ b/.github/workflows/notify.js @@ -6,9 +6,10 @@ const { hasPath, dedupComment } = require('./utils') const testFilesMessage = - 'This pull request modifies code in src/ but no tests were added/updated. Confirm whether tests should be added or ensure the PR description explains why tests are not required.' + '- This pull request modifies code in `src/*` but no tests were added/updated.\n - Confirm whether tests should be added or ensure the PR description explains why tests are not required.\n' -const changelogMessage = `This pull request implements a feature or fix, so it must include a changelog entry. See [CONTRIBUTING.md#changelog](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#changelog) for instructions.` +const changelogMessage = + '- This pull request implements a `feat` or `fix`, so it must include a changelog entry (unless the fix is for an *unreleased* feature). Review the [changelog guidelines](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#changelog).\n - Note: beta or "experiment" features that have active users *should* announce fixes in the changelog.\n - If this is not a feature or fix, use an appropriate type from the [title guidelines](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#pull-request-title). For example, telemetry-only changes should use the `telemetry` type.\n' /** * Remind partner teams that tests are required. We don't need to remind them if: @@ -44,12 +45,16 @@ module.exports = async ({ github, context }) => { issue_number: pullRequestId, }) + let msg = '' if (shouldAddTestFileMessage) { - await dedupComment({ github, comments, owner, repo, pullRequestId, message: testFilesMessage }) + msg += testFilesMessage } - if (shouldAddChangelogMessage) { - await dedupComment({ github, comments, owner, repo, pullRequestId, message: changelogMessage }) + msg += changelogMessage + } + + if (shouldAddTestFileMessage || shouldAddChangelogMessage) { + await dedupComment({ github, comments, owner, repo, pullRequestId, message: msg }) } } @@ -69,7 +74,12 @@ function requiresChangelogMessage(filenames, title) { * Require the test files message if there are changes to source files but aren't any * changes to the test files */ -function requiresTestFilesMessage(filenames) { +function requiresTestFilesMessage(filenames, title) { + if (/^\s*[mM]erge/.test(title)) { + console.log('"Merge" pull request') + return + } + // Check if src directory changed if (!hasPath(filenames, 'src/')) { console.log('Did not find src files in the code changes') diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c0e946609f9..c4dba3042e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -188,11 +188,11 @@ To run a single test in VSCode, do any one of: - Unix/macOS/POSIX shell: ``` - TEST_FILE=src/test/foo.test.ts npm run test + TEST_FILE=../core/src/test/foo.test.ts npm run test ``` - Powershell: ``` - $Env:TEST_FILE = "src/test/foo.test.ts"; npm run test + $Env:TEST_FILE = "../core/src/test/foo.test.ts"; npm run test ``` - To run all tests in a particular subdirectory, you can edit @@ -209,16 +209,26 @@ To run tests against a specific folder in VSCode, do any one of: - Run in your terminal - Unix/macOS/POSIX shell: ``` - TEST_DIR=src/test/foo npm run test + TEST_DIR=../core/src/test/foo npm run test ``` - Powershell: ``` - $Env:TEST_DIR = "src/test/foo"; npm run test + $Env:TEST_DIR = "../core/src/test/foo"; npm run test ``` +#### Run jscpd ("Copy-Paste Detection") + +If the "Copy-Paste Detection" CI job fails, you will find it useful to check things locally. To +check a specific file: + + npx jscpd --config .github/workflows/jscpd.json --pattern packages/…/src/foo.ts + +See the [jscpd cli documentation](https://github.com/kucherenko/jscpd/tree/master/apps/jscpd) for +more options. + ### Coverage report -You can find the coverage report at `./coverage/amazonq/lcov-report/index.html` and `./coverage/core/lcov-report/index.html` after running the tests. Tests ran from the workspace launch config won't generate a coverage report automatically because it can break file watching. +You can find the coverage report at `./coverage/amazonq/lcov-report/index.html` and `./coverage/toolkit/lcov-report/index.html` after running the tests. Tests ran from the workspace launch config won't generate a coverage report automatically because it can break file watching. ### CodeCatalyst Blueprints @@ -228,9 +238,12 @@ You can find documentation to create VSCode IDE settings for CodeCatalyst bluepr Before sending a pull request: +1. Treat all work as PUBLIC. Private `feature/x` branches will _not_ be squash-merged at release time. This has several benefits: + - Avoids mistakes (accidental exposure to public)! + - Avoids needing to erase (squash-merge) history. 1. Check that you are working against the latest source on the `master` branch. -2. Check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. Open an issue to discuss any significant work. +1. Check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. +1. Open an issue to discuss any significant work. To send a pull request: @@ -345,14 +358,16 @@ The `aws.dev.forceDevMode` setting enables or disables Toolkit "dev mode". Witho - Example: `getLogger().error('topic: widget failed: %O', { foo: 'bar', baz: 42 })` - Log messages are written to the extension Output channel, which you can view in vscode by visiting the "Output" panel and selecting `AWS Toolkit Logs` or `Amazon Q Logs`. - Use the `aws.dev.logfile` setting to set the logfile path to a fixed location, so you can follow - and filter logs using shell tools like `tail` and `grep`. For example in settings.json, - ``` - "aws.dev.logfile": "~/awstoolkit.log", - ``` - then you can tail the logfile in your terminal: - ``` - tail -F ~/awstoolkit.log - ``` + and filter logs using shell tools like `tail` and `grep`. + - Note: this always logs at **debug log-level** (though you can temporarily override that from the `AWS Toolkit Logs` UI). + - Example `settings.json`: + ``` + "aws.dev.logfile": "~/awstoolkit.log", + ``` + then you can tail the logfile in your terminal: + ``` + tail -F ~/awstoolkit.log + ``` - Use the `AWS (Developer): Watch Logs` command to watch and filter Toolkit logs (including telemetry) in VSCode. - Only available if you enabled "dev mode" (`aws.dev.forceDevMode` setting, see above). diff --git a/README.md b/README.md index c53bfa121f7..bd9f82fa04d 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,11 @@ AWS Toolkit is a [VS Code extension](https://marketplace.visualstudio.com/itemde - Connect with [IAM credentials](https://docs.aws.amazon.com/sdkref/latest/guide/access-users.html), [IAM Identity Center (SSO)](https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html), or [AWS Builder ID](https://docs.aws.amazon.com/signin/latest/userguide/differences-aws_builder_id.html) +- Connect VSCode to your EC2 instances - Connect to your [CodeCatalyst](https://codecatalyst.aws/) Dev Environments - Debug your Lambda functions using [SAM CLI](https://github.com/aws/aws-sam-cli) - Check and autocomplete code in SAM/CFN (CloudFormation) `template.yaml` files -- `Open Terminal` on your ECS tasks +- `Open Terminal` on your EC2 instances or ECS tasks - `Search Log Group` on your CloudWatch logs - Browse your AWS resources diff --git a/buildspec/lint.yml b/buildspec/lint.yml index 7a03a10e262..d44799d1186 100644 --- a/buildspec/lint.yml +++ b/buildspec/lint.yml @@ -26,7 +26,8 @@ phases: build: commands: - export HOME=/home/codebuild-user - - npm run testCompile + - npm run compile -w packages/core + - npm run testCompile -w packages/ --if-present - npm run lint - VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}" - CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g') # Encode `#` in the URL because otherwise the url is clipped in the Codecov.io site diff --git a/buildspec/linuxE2ETests.yml b/buildspec/linuxE2ETests.yml index 2413908988f..af3cfe71bde 100644 --- a/buildspec/linuxE2ETests.yml +++ b/buildspec/linuxE2ETests.yml @@ -37,11 +37,11 @@ phases: commands: - export HOME=/home/codebuild-user # Ignore failure until throttling issues are fixed. - - xvfb-run npm run testE2E || true + - xvfb-run npm run testE2E - VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}" - CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g') - CI_BUILD_ID="${CODEBUILD_BUILD_ID}" - - test -n "${CODECOV_TOKEN}" && [ "$TARGET_BRANCH" = "master" ] && ./codecov --token=${CODECOV_TOKEN} --branch=${CODEBUILD_RESOLVED_SOURCE_VERSION} --repository=${CODEBUILD_SOURCE_REPO_URL} --file=./coverage/core/lcov.info --file=./coverage/amazonq/lcov.info --file=./coverage/toolkit/lcov.info + - test -n "${CODECOV_TOKEN}" && [ "$TARGET_BRANCH" = "master" ] && ./codecov --token=${CODECOV_TOKEN} --branch=${CODEBUILD_RESOLVED_SOURCE_VERSION} --repository=${CODEBUILD_SOURCE_REPO_URL} --file=./coverage/amazonq/lcov.info --file=./coverage/toolkit/lcov.info finally: - rm -rf ~/.aws/sso/cache || true reports: diff --git a/buildspec/linuxIntegrationTests.yml b/buildspec/linuxIntegrationTests.yml index 31153176e9c..a40606bf8b3 100644 --- a/buildspec/linuxIntegrationTests.yml +++ b/buildspec/linuxIntegrationTests.yml @@ -21,7 +21,7 @@ phases: install: run-as: root runtime-versions: - nodejs: 16 + nodejs: 18 dotnet: 6.0 java: latest @@ -51,6 +51,27 @@ phases: # Ensure that "docker" group has permissions to the socket. # - chown codebuild-user /var/run/docker.sock - chmod 666 /var/run/docker.sock + # Pull Docker Images for SAM tests + + # Nodejs + - | + docker pull public.ecr.aws/sam/build-nodejs18.x:latest + docker pull public.ecr.aws/sam/build-nodejs20.x:latest + docker pull public.ecr.aws/sam/build-nodejs22.x:latest + # Java + - | + docker pull public.ecr.aws/sam/build-java8.al2:latest + docker pull public.ecr.aws/sam/build-java11:latest + docker pull public.ecr.aws/sam/build-java17:latest + # Python + - | + docker pull public.ecr.aws/sam/build-python3.10:latest + docker pull public.ecr.aws/sam/build-python3.11:latest + docker pull public.ecr.aws/sam/build-python3.12:latest + docker pull public.ecr.aws/sam/build-python3.13:latest + # Dotnet + - | + docker pull public.ecr.aws/sam/build-dotnet6:latest pre_build: commands: @@ -75,7 +96,7 @@ phases: - VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}" - CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g') - CI_BUILD_ID="${CODEBUILD_BUILD_ID}" - - test -n "${CODECOV_TOKEN}" && [ "$TARGET_BRANCH" = "master" ] && ./codecov --token=${CODECOV_TOKEN} --branch=${CODEBUILD_RESOLVED_SOURCE_VERSION} --repository=${CODEBUILD_SOURCE_REPO_URL} --file=./coverage/core/lcov.info --file=./coverage/amazonq/lcov.info --file=./coverage/toolkit/lcov.info + - test -n "${CODECOV_TOKEN}" && [ "$TARGET_BRANCH" = "master" ] && ./codecov --token=${CODECOV_TOKEN} --branch=${CODEBUILD_RESOLVED_SOURCE_VERSION} --repository=${CODEBUILD_SOURCE_REPO_URL} --file=./coverage/amazonq/lcov.info --file=./coverage/toolkit/lcov.info post_build: commands: # Destroy .netrc to avoid leaking $GITHUB_READONLY_TOKEN. diff --git a/buildspec/linuxTests.yml b/buildspec/linuxTests.yml index 41f84d2b237..d8fff088c2f 100644 --- a/buildspec/linuxTests.yml +++ b/buildspec/linuxTests.yml @@ -18,7 +18,7 @@ phases: install: run-as: root runtime-versions: - nodejs: 16 + nodejs: 18 commands: - bash buildspec/shared/linux-install.sh @@ -48,7 +48,7 @@ phases: - VCS_COMMIT_ID="${CODEBUILD_RESOLVED_SOURCE_VERSION}" - CI_BUILD_URL=$(echo $CODEBUILD_BUILD_URL | sed 's/#/%23/g') # Encode `#` in the URL because otherwise the url is clipped in the Codecov.io site - CI_BUILD_ID="${CODEBUILD_BUILD_ID}" - - test -n "${CODECOV_TOKEN}" && [ "$TARGET_BRANCH" = "master" ] && ./codecov --token=${CODECOV_TOKEN} --branch=${CODEBUILD_RESOLVED_SOURCE_VERSION} --repository=${CODEBUILD_SOURCE_REPO_URL} --file=./coverage/core/lcov.info --file=./coverage/amazonq/lcov.info --file=./coverage/toolkit/lcov.info + - test -n "${CODECOV_TOKEN}" && [ "$TARGET_BRANCH" = "master" ] && ./codecov --token=${CODECOV_TOKEN} --branch=${CODEBUILD_RESOLVED_SOURCE_VERSION} --repository=${CODEBUILD_SOURCE_REPO_URL} --file=./coverage/amazonq/lcov.info --file=./coverage/toolkit/lcov.info reports: unit-test: diff --git a/buildspec/packageTestVsix.yml b/buildspec/packageTestVsix.yml index e10fbebbf90..73f15c0ba10 100644 --- a/buildspec/packageTestVsix.yml +++ b/buildspec/packageTestVsix.yml @@ -13,7 +13,7 @@ phases: install: run-as: root runtime-versions: - nodejs: 16 + nodejs: 18 commands: - bash buildspec/shared/linux-install.sh diff --git a/buildspec/release/35opengate.yml b/buildspec/release/35opengate.yml new file mode 100644 index 00000000000..45362ac14e3 --- /dev/null +++ b/buildspec/release/35opengate.yml @@ -0,0 +1,18 @@ +version: 0.2 + +phases: + install: + runtime-versions: + nodejs: 16 + + pre_build: + commands: + - STAGE_NAME=SourceWithGit + - PIPELINE=$(echo $CODEBUILD_INITIATOR | sed -e 's/codepipeline\///') + build: + commands: + - | + aws codepipeline enable-stage-transition \ + --pipeline-name "$PIPELINE" \ + --stage-name "$STAGE_NAME" \ + --transition-type "Inbound" diff --git a/buildspec/shared/common.sh b/buildspec/shared/common.sh index f2c08626fd5..a0dbc2e5837 100644 --- a/buildspec/shared/common.sh +++ b/buildspec/shared/common.sh @@ -6,13 +6,9 @@ # Ignore these patterns when deciding if the build should fail. # - "waiting for browser": from `ssoAccessTokenProvider.test.ts`, unclear how to fix it. -# - "Webview is disposed": only happens on vscode "minimum" (1.68.0) # - "HTTPError: Response code …": caused by github rate-limiting. # - "npm WARN deprecated querystring": transitive dep of aws sdk v2 (check `npm ls querystring`), so that's blocked until we migrate to v3. -_ignore_pat='Timed-out waiting for browser login flow\|HTTPError: Response code 403\|HTTPError: Response code 404\|npm WARN deprecated querystring' -if [ "$VSCODE_TEST_VERSION" = 'minimum' ]; then - _ignore_pat="$_ignore_pat"'\|Webview is disposed' -fi +_ignore_pat='HTTPError: Response code 403\|HTTPError: Response code 404\|npm WARN deprecated querystring\|npm WARN deprecated' # Do not print (noisy) lines matching these patterns. # - "ERROR:bus… Failed to connect to the bus": noise related to "xvfb". https://github.com/cypress-io/cypress/issues/19299 diff --git a/buildspec/shared/linux-pre_build.sh b/buildspec/shared/linux-pre_build.sh index 25e9f0c879f..102103ff30c 100644 --- a/buildspec/shared/linux-pre_build.sh +++ b/buildspec/shared/linux-pre_build.sh @@ -22,5 +22,5 @@ if [ "$TOOLKITS_CODEARTIFACT_DOMAIN" ] && [ "$TOOLKITS_CODEARTIFACT_REPO" ] && [ fi # TODO: move this to the "install" phase? -export NODE_OPTIONS=--max-old-space-size=8192 +export NODE_OPTIONS='--max-old-space-size=8192' npm 2>&1 ci | run_and_report 2 'npm WARN deprecated' 'Deprecated dependencies must be updated.' diff --git a/buildspec/windowsTests.yml b/buildspec/windowsTests.yml index bc182bec1e1..4b19dec5eb2 100644 --- a/buildspec/windowsTests.yml +++ b/buildspec/windowsTests.yml @@ -5,7 +5,7 @@ env: phases: install: runtime-versions: - nodejs: 16 + nodejs: 18 commands: - | if(-Not($Env:CODECOV_TOKEN -eq $null)) { @@ -30,7 +30,8 @@ phases: build: commands: - - npm run testCompile + - npm run compile -w packages/core + - npm run testCompile -w packages/ --if-present - npm run lint - $env:TEST_REPORT_DIR="$env:CODEBUILD_SRC_DIR/.test_reports"; npm run test - | diff --git a/codecov.yml b/codecov.yml index caf79e23a73..1e348859f11 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,6 +5,12 @@ codecov: notify: require_ci_to_pass: no +ignore: + - 'node_modules/' + - 'plugins/' + - 'scripts/' + - 'src.gen/' + coverage: precision: 2 round: down @@ -12,38 +18,80 @@ coverage: status: project: + # Note: `default` measures the entire project. + # It does NOT define defaults "inherited" by other `project` items. + # https://docs.codecov.com/docs/commit-status default: - target: 70% - threshold: 5% - informational: true + informational: true # Always pass. Ignore `target`, `threshold`, etc. + target: 80 + threshold: 5 + only_pulls: true codewhisperer: + informational: true # Always pass. Ignore `target`, `threshold`, etc. paths: - - packages/core/src/codewhisperer/* + - 'packages/core/src/codewhisperer/' + - 'packages/core/src/amazonq*/*' + - 'packages/amazonq/src/' flags: - 'codewhisperer' + target: 80 + threshold: 5 + only_pulls: true amazonqFeatureDev: + informational: true # Always pass. Ignore `target`, `threshold`, etc. paths: - - packages/core/src/amazonqFeatureDev/* + - 'packages/core/src/amazonqFeatureDev/*' flags: - 'amazonqFeatureDev' + target: 80 + threshold: 5 + only_pulls: true amazonqGumby: + informational: true # Always pass. Ignore `target`, `threshold`, etc. paths: - - packages/core/src/amazonqGumby/* + - 'packages/core/src/amazonqGumby/*' + target: 80 + threshold: 5 + only_pulls: true codewhispererChat: + informational: true # Always pass. Ignore `target`, `threshold`, etc. paths: - - packages/core/src/codewhispererChat/* + - 'packages/core/src/codewhispererChat/*' + target: 80 + threshold: 5 + only_pulls: true applicationcomposer: + informational: true # Always pass. Ignore `target`, `threshold`, etc. paths: - - packages/core/src/applicationcomposer/* + - 'packages/core/src/applicationcomposer/*' + target: 80 + threshold: 5 + only_pulls: true stepFunctions: - target: 50% - threshold: 10% + informational: true # Always pass. Ignore `target`, `threshold`, etc. paths: - - packages/core/src/stepFunctions/* + - 'packages/core/src/stepFunctions/*' + target: 50 + threshold: 10 + only_pulls: true threatComposer: + informational: true # Always pass. Ignore `target`, `threshold`, etc. paths: - - packages/core/src/threatComposer/* - patch: false + - 'packages/core/src/threatComposer/*' + target: 80 + threshold: 5 + only_pulls: true + patch: + default: + # Note: `default` measures the entire project. + # It does NOT define defaults "inherited" by other `project` items. + # https://docs.codecov.com/docs/commit-status + target: 90 + threshold: 5 + only_pulls: true + informational: false # Fail if PR changes are not covered. + # branches: + # - master changes: false comment: false @@ -54,7 +102,9 @@ github_checks: flags: codewhisperer: paths: - - packages/core/src/codewhisperer/ + - 'packages/core/src/codewhisperer/' + - 'packages/core/src/amazonq*/*' + - 'packages/amazonq/src/' amazonqFeatureDev: paths: - - packages/core/src/amazonqFeatureDev/ + - 'packages/core/src/amazonqFeatureDev/' diff --git a/docs/TESTPLAN.md b/docs/TESTPLAN.md index 59f86404385..56a299c9a5b 100644 --- a/docs/TESTPLAN.md +++ b/docs/TESTPLAN.md @@ -42,11 +42,15 @@ The test suite has the following categories of tests: - Live in `src/testE2E` - These tests are heavier than Integration tests. - Some E2E tests have a more complicated architecture, described in [TEST_E2E](./TEST_E2E.md) +- Performance Tests: **slow** tests + - Live in `src/testInteg/perf`. + - A subset of integration tests focused on catching performance regressions. + - Use a combination of operation counting and system usage statistics such as cpu usage, memory usage, and duration. + - Each test is often repeated 10 or more times for less variant system usage statistics, then median of runs is used. ## Test files -Currently, most if not all testing code lives in the subproject `packages/core/`. -For more information, see [arch_develop.md](./arch_develop.md#monorepo-structure) +Currently, most testing code lives in the subproject `packages/core/` due to the move to monorepo. See [arch_develop.md](./arch_develop.md#monorepo-structure). - `src/test/` : unit tests - `src/test/globalSetup.test.ts` : @@ -60,6 +64,8 @@ For more information, see [arch_develop.md](./arch_develop.md#monorepo-structure - `.vscode/launch.json` : defines VSCode launch configs useful for Toolkit developers, e.g. the `Extension Tests` config runs all tests in `src/test/`. +Many tests required running in an activated extension environment, but the core-lib is just a library and does not have any extension to activate itself. Core-lib tests are run from `packages/toolkit`, and web tests are run from `packages/amazonq`. + ## How we test VSCode documentation describes an [extension testing](https://code.visualstudio.com/api/working-with-extensions/testing-extension) diff --git a/docs/arch_develop.md b/docs/arch_develop.md index 1d9a5d8f48a..a3ad998d3e1 100644 --- a/docs/arch_develop.md +++ b/docs/arch_develop.md @@ -23,7 +23,7 @@ If you are considering contributing, please consider whether your implementation library or in `packages/toolkit`. If your work could be re-used by other packages (e.g. auth mechanisms, utilities), then it may belong in the core library. If instead you are adding something toolkit specific (eg. an integration to a new AWS service in the Explorer Tree), consider putting it in `packages/toolkit`. -To import from the core library, please export your desired code using `index.ts` files, and add an appropriate `exports` statement +To import from the core library, please export your desired code using `index.ts` files and add an appropriate `exports` statement in `packages/core/package.json`. Unless otherwise stated, the documentation throughout this project is referring to the code and @@ -51,25 +51,9 @@ Current quirks of the current monorepo status that should be resolved/evaluated - Pre-release only publishes packages/toolkit extension directly. It should be extended to other added extensions. See [`release.yml`](../.github/workflows/release.yml) - VSCode does not support inheriting/extending `.vscode/` settings: https://github.com/microsoft/vscode/issues/15909 -Additional quirks introduced by creating a core library from the original extension code: - -- Tests are ran from `packages/core/` -- Extension runs from `packages/toolkit` -- Extension tests run from the core lib. Since some of the tests require an extension context/sandbox, we initiate a "fake" extension to run these tests. This is also why there are vscode extension properties in the package.json -- Some of original extension code (that now lives in `packages/core`) depends on the package.json, specifically the contributes section. This section is very large AND needs to be present in both the core library and toolkit extension package.jsons. The core library code needs access to this section to create types, set up SAM debuggers, etc. The toolkit needs this section during packaging/debugging so that the extension can run in vscode. The short term solution was to creat a [build script](../packages/toolkit/scripts/build/handlePackageJson.ts) to copy necessary fields over to the toolkit extension during packaging and debugging. - ### Contributes and Settings -Some components of the core library depend on the `package.json`s of the extensions. One example of this is compile time checking of the extension's settings values. However, VSCode also requires a complete local `package.json` for the individual extensions during packaging. As a temporary workaround to this, we are using scripts to auto-populate the `package.json`s for the individual extensions from the core `package.json`. - -- [`packages/toolkit/../handlePackageJson.ts`](../packages/toolkit/scripts/build/handlePackageJson.ts) - - Copies the entirety of the `contributes` and `engine` sections, except for `configuration.properties` relating to `packages/amazon`. - - Restores to the original barebones `package.json` after packaging/debugging, to avoid a large amount of duplicate code. - - To develop for the Toolkit extension: add all changes to `packages/core/package.json` -- [`packages/amazonq/../syncPackageJson.ts`](../packages/amazonq/scripts/build/syncPackageJson.ts) - - Moves all Amazon Q related `configuration.properties` to the local `package.json` only, overwriting anything that exists with the same name locally. - - Does not restore, it is a superset of what exists in `packages/core` for `configuration.properties`. - - To develop for the Amazon Q extension: add all changes to `packages/amazonq/package.json`, EXCEPT for settings that are references by code in the core library, or settings that already exist in the core `package.json` +`packages/toolkit/` and `packages/amazonq` have independent extension packageJSON files. They do not rely on `packages/core/package.json`. However, to get typed icons in the core-lib we require a place to store the icon entries. This currently happens in `packages/core/package.json`. See [`icons.md`](./icons.md) for more information. If you are modifying or registering new debuggers in VS Code via the `debuggers` contribution point, you may need to regenerate the [definitions file](../packages/core/src/shared/sam/debugger/awsSamDebugConfiguration.gen.ts). After updating ['toolkit/package.json'](../packages/toolkit/package.json), run `npm run generateConfigurationAttributes -w packages/toolkit` @@ -460,7 +444,7 @@ await tester.result(items[0].data) // Execute the actions, asserting the final r Abstractly, a 'wizard' is a collection of discrete, linear steps (subroutines), where each step can potentially be dependent on prior steps, that results in some final state. Wizards are extremely common in top-level flows such as creating a new resource, deployments, or confirmation messages. For these kinds of flows, we have a shared `Wizard` class that handles the bulk of control flow and state management logic for us. -### Creating a Wizard (Quick Picks) +### 1. `Wizard` Class Create a new wizard by extending the base `Wizard` class, using the template type to specify the shape of the wizard state. All wizards have an internal `form` property that is used to assign @@ -498,6 +482,41 @@ class ExampleWizard extends Wizard { } ``` +### 2. `CompositeWizard` Class + +`CompositeWizard` extends `Wizard` to create and manage a collection of nested/child wizards. + +Extend this class to create a wizard that contains other wizards as part of a prompter flow. +Use `this.createWizardPrompter()` to use a wizard as a prompter in the `CompositeWizard`. + +Example: + +```ts + +// Child wizard +class ChildWizard extends Wizard {...} + + +// Composite wizard +interface SingleNestedWizardForm { + ... + singleNestedWizardNestedProp: string + ... +} + +class SingleNestedWizard extends CompositeWizard { + constructor() { + super() + ... + this.form.singleNestedWizardNestedProp.bindPrompter(() => + this.createWizardPrompter(ChildWizard) + ) + ... + } +} + +``` + ### Executing Wizards can be ran by calling the async `run` method: @@ -511,6 +530,8 @@ Note that all wizards can potentially return `undefined` if the workflow was can ### Testing +#### Using `WizardTester` + Use `createWizardTester` on an instance of a wizard. Tests can then be constructed by asserting both the user-defined and internal state. Using the above `ExampleWizard`: ```ts @@ -521,6 +542,70 @@ tester.foo.applyInput('Hello, world!') // Manipulate 'user' state tester.bar.assertShow() // True since 'foo' has a defined value ``` +#### Using `PrompterTester` + +Use `PrompterTester` to simulate user behavior (click, input and selection) on prompters to test end-to-end flow of a wizard. + +Example: + +```ts +// 1. Register PrompterTester handlers +const prompterTester = PrompterTester.init() + .handleInputBox('Input Prompter title 1', (inputBox) => { + // Register Input Prompter handler + inputBox.acceptValue('my-source-bucket-name') + }) + .handleQuickPick('Quick Pick Prompter title 2', (quickPick) => { + // Register Quick Pick Prompter handler + + // Optional assertion can be added as part of the handler function + assert.strictEqual(quickPick.items.length, 2) + assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') + assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') + // Choose item + quickPick.acceptItem(quickPick.items[0]) + }) + .handleQuickPick( + 'Quick Pick Prompter with various handler behavior title 3', + (() => { + // Register handler with dynamic behavior + const generator = (function* () { + // First call, choose '**' + yield async (picker: TestQuickPick) => { + await picker.untilReady() + assert.strictEqual(picker.items[1].label, '**') + picker.acceptItem(picker.items[1]) + } + // Second call, choose BACK button + yield async (picker: TestQuickPick) => { + await picker.untilReady() + picker.pressButton(vscode.QuickInputButtons.Back) + } + // Third and subsequent call + while (true) { + yield async (picker: TestQuickPick) => { + await picker.untilReady() + picker.acceptItem(picker.items[1]) + } + } + })() + + return (picker: TestQuickPick) => { + const next = generator.next().value + return next(picker) + } + })() + ) + .build() + +// 2. Run your wizard class +const result = await wizard.run() + +// 3. Assert your tests +prompterTester.assertCallAll() +prompterTester.assertCallOrder('Input Prompter title 1', 1) +``` + ## Module path debugging Node has an environment variable `NODE_DEBUG=module` that helps to debug module imports. This can be helpful on windows, which can load node modules into uppercase or lower case drive letters, depending on the drive letter of the parent module. diff --git a/docs/arch_features.md b/docs/arch_features.md index c76874f7b6e..7a9c239d584 100644 --- a/docs/arch_features.md +++ b/docs/arch_features.md @@ -41,8 +41,11 @@ For connecting a new VSCode _terminal_, remote connect works like this: For EC2 specifically, there are a few additional steps: +1. Remote window connections are only supported for EC2 instances running a linux based OS such as Amazon Linux or Ubuntu. However, the terminal option is supported by all OS, and will open a Powershell-based terminal for Windows instances. 1. If connecting to EC2 instance via remote window, the toolkit generates temporary SSH keys (30 second lifetime), with the public key sent to the remote instance. - Key type is ed25519 if supported, or RSA otherwise. + - Lines in `.ssh/authorized_keys` marked with the comment `#AWSToolkitForVSCode` will be removed by AWS Toolkit. + - Assumes `.sss/authorized_keys` can be found under `/home/ec2-user/` on Amazon Linux and `/home/ubuntu/` on Ubuntu. 1. If insufficient permissions are detected on the attached IAM role, toolkit will prompt to add an inline policy with the necessary actions. 1. If SSM sessions remain open after closing the window/terminal, the toolkit will terminate them on-shutdown, or when starting another session to the same instance. diff --git a/docs/build.md b/docs/build.md index 8c6de3a9e84..ffa9e1a8a81 100644 --- a/docs/build.md +++ b/docs/build.md @@ -1,11 +1,23 @@ # Build -The AmazonQ features rely on the `codewhisperer-streaming` service, who's client -is generated from the service's smithy models and placed in -`src.gen/@amzn/codewhisperer-streaming` (For more -information about this client and how it is generated, please see this +The AmazonQ features rely on the `codewhisperer-streaming` service, to support both Sigv4 and Bearer token modes of this service, +two clients are generated from the service's smithy models and placed in +`src.gen/@amzn/amazon-q-developer-streaming-client` and `src.gen/@amzn/codewhisperer-streaming` respectively (For more +information about these clients and how they are generated, please see this [quip document](https://quip-amazon.com/2dAWAvTIYXXr/Build-instructions-for-AWS-CodeWhisperer-Streaming-Typescript-client)). +## @amzn/amazon-q-developer-streaming client + +This client is a standalone npm project in typescript, and it is added to +the project as a workspace in the project's root `package.json` with the line `"workspaces": [ ..., "src.gen/@amzn/amazon-q-developer-streaming" ]`. +The client may be manually built using `npm run build -w @amzn/amazon-q-developer-streaming"`. +The `generateClients` run script ensures that this dependency is +built before the toolkit project itself. Workspaces are automatically ready to +be imported in the root toolkit project by their declared package.json name, +(`@amzn/amazon-q-developer-streaming` in this case). + +## @amzn/codewhisperer-streaming client + This client is a standalone npm project in typescript, and it is added to the project as a workspace in the project's root `package.json` with the line `"workspaces": [ ..., "src.gen/@amzn/codewhisperer-streaming" ]`. The client may be manually built using `npm run build -w @amzn/codewhisperer-streaming"`. diff --git a/docs/marketplace/vscode/infraComposer.webp b/docs/marketplace/vscode/infraComposer.webp new file mode 100644 index 00000000000..ff417f35537 Binary files /dev/null and b/docs/marketplace/vscode/infraComposer.webp differ diff --git a/docs/telemetry-perf.md b/docs/telemetry-perf.md new file mode 100644 index 00000000000..892ba8f6a57 --- /dev/null +++ b/docs/telemetry-perf.md @@ -0,0 +1,613 @@ +# Telemetry Performance Metrics + +Visual representations of performance telemetry metrics + +## Amazon Q Inline + +### codewhispererFirstCompletionLatency + +How long it took to receive the first suggestion after we started calling the getRecommendations API + +```mermaid + sequenceDiagram + participant User + participant invoke as Inline invoked + participant rService as Recommendation Service + participant rHandler as Recommendation Handler + participant backend as CWSPR backend + participant sdk as Create CWSPR SDK + participant token as Toolkit auth + + + User->>invoke: Finished typing + invoke->>rService: calls + rService->>rHandler: calls + rHandler->>sdk: calls + + sdk->>token: Start getting bearer token + token->>sdk: Finished getting bearer token + + sdk->>rHandler: Return client + note over rHandler, backend: codewhispererFirstCompletionLatency + rect rgb(230, 230, 230, 0.5) + loop Get paginated recommendations + rHandler->>backend: calls + end + backend->>rHandler: first response received + end + rHandler->>User: show results + backend->>rHandler: all the other responses + rHandler->>User: add to already shown results +``` + +### codewhispererEndToEndLatency + +How long it took from when we started calling the getRecommendations API to when the first suggestion was shown + +```mermaid + sequenceDiagram + participant User + participant invoke as Inline invoked + participant rService as Recommendation Service + participant rHandler as Recommendation Handler + participant backend as CWSPR backend + participant sdk as Create CWSPR SDK + participant token as Toolkit auth + + User->>invoke: Finished typing + invoke->>rService: calls + rService->>rHandler: calls + rHandler->>sdk: calls + + sdk->>token: Start getting bearer token + token->>sdk: Finished getting bearer token + + sdk->>rHandler: Return client + note over User, backend: codewhispererEndToEndLatency + rect rgb(230, 230, 230, 0.5) + loop Get paginated recommendations + rHandler->>backend: calls + end + backend->>rHandler: first response received + rHandler->>User: show results + end + + backend->>rHandler: all the other responses + rHandler->>User: add to already shown results +``` + +### codewhispererAllCompletionsLatency + +How long it took to complete all paginated calls + +```mermaid + sequenceDiagram + participant User + participant invoke as Inline invoked + participant rService as Recommendation Service + participant rHandler as Recommendation Handler + participant backend as CWSPR backend + participant sdk as Create CWSPR SDK + participant token as Toolkit auth + + + User->>invoke: Finished typing + invoke->>rService: calls + rService->>rHandler: calls + rHandler->>sdk: calls + + sdk->>token: Start getting bearer token + token->>sdk: Finished getting bearer token + + sdk->>rHandler: Return client + note over User, backend: codewhispererAllCompletionsLatency + rect rgb(230, 230, 230, 0.5) + loop Get paginated recommendations + rHandler->>backend: calls + end + backend->>rHandler: first response received + rHandler->>User: show results + backend->>rHandler: all the other responses + end + + + rHandler->>User: add to already shown results +``` + +### codewhispererPostprocessingLatency + +How long it took to display the first suggestion after it received the first response from the API + +```mermaid + sequenceDiagram + participant User + participant invoke as Inline invoked + participant rService as Recommendation Service + participant rHandler as Recommendation Handler + participant backend as CWSPR backend + participant sdk as Create CWSPR SDK + participant token as Toolkit auth + + + User->>invoke: Finished typing + invoke->>rService: calls + rService->>rHandler: calls + rHandler->>sdk: calls + + sdk->>token: Start getting bearer token + token->>sdk: Finished getting bearer token + + sdk->>rHandler: Return client + loop Get paginated recommendations + rHandler->>backend: calls + end + note over User, backend: codewhispererPostprocessingLatency + rect rgb(230, 230, 230, 0.5) + backend->>rHandler: first response received + rHandler->>User: show results + end + + backend->>rHandler: all the other responses + rHandler->>User: add to already shown results +``` + +### codewhispererCredentialFetchingLatency + +How long it took to get the bearer token + +```mermaid + sequenceDiagram + participant User + participant invoke as Inline invoked + participant rService as Recommendation Service + participant rHandler as Recommendation Handler + participant backend as CWSPR backend + participant sdk as Create CWSPR SDK + participant token as Toolkit auth + + User->>invoke: Finished typing + invoke->>rService: calls + rService->>rHandler: calls + rHandler->>sdk: calls + + note over sdk, token: codewhispererCredentialFetchingLatency + rect rgb(230, 230, 230, 0.5) + sdk->>token: Start getting bearer token + token->>sdk: Finished getting bearer token + end + sdk->>rHandler: Return client + loop Get paginated recommendations + rHandler->>backend: calls + end + + backend->>rHandler: first response received + rHandler->>User: show results + + backend->>rHandler: all the other responses + rHandler->>User: add to already shown results +``` + +### codewhispererPreprocessingLatency + +How long it took to create the client and get ready to start sending getRecommendation API calls + +```mermaid + sequenceDiagram + participant User + participant invoke as Inline invoked + participant rService as Recommendation Service + participant rHandler as Recommendation Handler + participant backend as CWSPR backend + participant sdk as Create CWSPR SDK + participant token as Toolkit auth + + User->>invoke: Finished typing + invoke->>rService: calls + rService->>rHandler: calls + rHandler->>sdk: calls + + note over rHandler, token: codewhispererPreprocessingLatency + rect rgb(230, 230, 230, 0.5) + sdk->>token: Start getting bearer token + token->>sdk: Finished getting bearer token + sdk->>rHandler: Return client + end + loop Get paginated recommendations + rHandler->>backend: calls + end + + backend->>rHandler: first response received + rHandler->>User: show results + + backend->>rHandler: all the other responses + rHandler->>User: add to already shown results +``` + +### codewhisperer_perceivedLatency duration + +How long it took from when the user stopped pressing a key to when they were shown a response + +```mermaid + sequenceDiagram + participant User + participant invoke as Inline invoked + participant rService as Recommendation Service + participant rHandler as Recommendation Handler + participant backend as CWSPR backend + participant sdk as Create CWSPR SDK + participant token as Toolkit auth + + User->>invoke: Finished typing + note over User, token: codewhisperer_perceivedLatency duration + rect rgb(230, 230, 230, 0.5) + invoke->>rService: calls + rService->>rHandler: calls + rHandler->>sdk: calls + sdk->>token: Start getting bearer token + token->>sdk: Finished getting bearer token + sdk->>rHandler: Return client + + loop Get paginated recommendations + rHandler->>backend: calls + end + + backend->>rHandler: first response received + rHandler->>User: show results + + backend->>rHandler: all the other responses + rHandler->>User: add to already shown results + end +``` + +## Amazon Q Chat + +### amazonq_chatRoundTrip + +Measures sequential response times in Q chat, from user input to message display. Tracks time intervals between key events: editor receiving the message, feature processing, and final message rendering + +```mermaid + sequenceDiagram + participant User + participant chat as Chat UI + participant vscode as VSCode + participant event as Event Recorder + participant partner as Partner team code + participant telemetry + + User->>chat: Write chat message and press enter + chat->>vscode: send message with timestamp + vscode->>event: record chatMessageSent/editorReceivedMessage timestamps + vscode->>partner: forward chat message + partner->>event: record featureReceivedMessage timestamp + partner->>partner: call backend/get response + partner->>vscode: forward response contents + vscode->>chat: display message + chat->>vscode: send stop-chat-message-telemetry event + vscode->>event: record messageDisplayed timestamp + event->>vscode: get the telemetry timestamps + vscode->>telemetry: emit amazonq_chatRoundTrip with telemetry timestamps +``` + +### cwsprChatTimeToFirstChunk + +The time between when the conversation stream is created and when we got back the first usable result + +```mermaid + sequenceDiagram + participant user as User + participant chat as Chat UI + participant vscode as VSCode extension host + participant generateResponse as Generate response + participant backend as Q service backend + + user->>chat: Presses enter with message + chat->>vscode: Tell VSCode to generate a response + vscode->>generateResponse: start generating + generateResponse->>backend: start stream + backend->>backend: create conversation id + backend->>generateResponse: get conversation id + note over backend, generateResponse: cwsprChatTimeToFirstChunk + rect rgb(230, 230, 230, 0.5) + backend->>backend: generate first chunk + backend->>generateResponse: chunk received + end + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + loop for each subsequent chunk + backend->>backend: generate next chunk + backend->>generateResponse: chunk received + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + end +``` + +### cwsprChatTimeBetweenChunks + +An array of time when successive pieces of data are received from the server + +```mermaid + sequenceDiagram + participant user as User + participant chat as Chat UI + participant vscode as VSCode extension host + participant generateResponse as Generate response + participant backend as Q service backend + + user->>chat: Presses enter with message + chat->>vscode: Tell VSCode to generate a response + vscode->>generateResponse: start generating + generateResponse->>backend: start stream + backend->>backend: create conversation id + backend->>generateResponse: get conversation id + + loop for each subsequent chunk + note over backend, generateResponse: cwsprChatTimeBetweenChunks + rect rgb(230, 230, 230, 0.5) + backend->>backend: generate next chunk + backend->>generateResponse: chunk received + generateResponse->>generateResponse: record timestamp + end + + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + end +``` + +### cwsprChatFullResponseLatency + +The time between when the conversation id was created and the final response from the server was received + +```mermaid + sequenceDiagram + participant user as User + participant chat as Chat UI + participant vscode as VSCode extension host + participant generateResponse as Generate response + participant backend as Q service backend + + user->>chat: Presses enter with message + chat->>vscode: Tell VSCode to generate a response + vscode->>generateResponse: start generating + generateResponse->>backend: start stream + backend->>backend: create conversation id + backend->>generateResponse: get conversation id + + note over backend, chat: cwsprChatFullResponseLatency + rect rgb(230, 230, 230, 0.5) + loop for each subsequent chunk + backend->>backend: generate next chunk + backend->>generateResponse: chunk received + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + end + backend->>generateResponse: final chunk received + end + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk +``` + +### cwsprChatTimeToFirstUsableChunk + +The time between the initial server request, including creating the conversation stream, and the first usable result + +```mermaid + sequenceDiagram + participant user as User + participant chat as Chat UI + participant vscode as VSCode extension host + participant generateResponse as Generate response + participant backend as Q service backend + + user->>chat: Presses enter with message + chat->>vscode: Tell VSCode to generate a response + vscode->>generateResponse: start generating + note over backend, generateResponse: cwsprChatTimeToFirstUsableChunk + rect rgb(230, 230, 230, 0.5) + generateResponse->>backend: start stream + backend->>backend: create conversation id + backend->>generateResponse: get conversation id + backend->>backend: generate first chunk + backend->>generateResponse: chunk received + end + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + loop for each subsequent chunk + backend->>backend: generate next chunk + backend->>generateResponse: chunk received + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + end +``` + +### cwsprChatFullServerResponseLatency + +The time between the initial server request, including creating the conversation stream, and the final response from the server + +```mermaid + sequenceDiagram + participant user as User + participant chat as Chat UI + participant vscode as VSCode extension host + participant generateResponse as Generate response + participant backend as Q service backend + + user->>chat: Presses enter with message + chat->>vscode: Tell VSCode to generate a response + vscode->>generateResponse: start generating + note over backend, chat: cwsprChatFullServerResponseLatency + rect rgb(230, 230, 230, 0.5) + generateResponse->>backend: start stream + backend->>backend: create conversation id + backend->>generateResponse: get conversation id + loop for each subsequent chunk + backend->>backend: generate next chunk + backend->>generateResponse: chunk received + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + end + backend->>generateResponse: final chunk received + end + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk +``` + +### cwsprChatTimeToFirstDisplay + +The time between the user pressing enter and when the first piece of data is displayed to the user + +```mermaid + sequenceDiagram + participant user as User + participant chat as Chat UI + participant vscode as VSCode extension host + participant generateResponse as Generate response + participant backend as Q service backend + note over backend, user: cwsprChatTimeToFirstDisplay + rect rgb(230, 230, 230, 0.5) + user->>chat: Presses enter with message + chat->>vscode: Tell VSCode to generate a response + vscode->>generateResponse: start generating + generateResponse->>backend: start stream + backend->>backend: create conversation id + backend->>generateResponse: get conversation id + backend->>backend: generate first chunk + backend->>generateResponse: chunk received + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + end + loop for each subsequent chunk + backend->>backend: generate next chunk + backend->>generateResponse: chunk received + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + end +``` + +### cwsprChatTimeBetweenDisplays + +An array of time when successive pieces of server responses are displayed to the user + +```mermaid + sequenceDiagram + participant user as User + participant chat as Chat UI + participant vscode as VSCode extension host + participant generateResponse as Generate response + participant backend as Q service backend + + user->>chat: Presses enter with message + chat->>vscode: Tell VSCode to generate a response + vscode->>generateResponse: start generating + generateResponse->>backend: start stream + backend->>backend: create conversation id + backend->>generateResponse: get conversation id + + note over backend, chat: cwsprChatTimeBetweenDisplays + rect rgb(230, 230, 230, 0.5) + loop for each subsequent chunk + backend->>backend: generate next chunk + backend->>generateResponse: chunk received + generateResponse->>vscode: send chunk to display + vscode->>chat: display chunk + chat->>vscode: record display timestamp + end + end +``` + +### cwsprChatFullDisplayLatency + +The time between the user pressing enter and the entire response being rendered + +```mermaid + sequenceDiagram + participant user as User + participant chat as Chat UI + participant vscode as VSCode extension host + participant generateResponse as Generate response + participant backend as Q service backend + + note over backend, user: cwsprChatFullDisplayLatency + rect rgb(230, 230, 230, 0.5) + user->>chat: Presses enter with message + chat->>vscode: Tell VSCode to generate a response + vscode->>generateResponse: start generating + generateResponse->>backend: start stream + backend->>backend: create conversation id + backend->>generateResponse: get conversation id + generateResponse->>backend: start stream + backend->>backend: create conversation id + loop for each subsequent chunk + backend->>backend: generate next chunk + backend->>vscode: send chunk to display + vscode->>chat: display chunk + end + end + +``` + +## Crash Monitoring + +We make an attempt to gather information regarding when the IDE crashes, then report it to telemetry. This is the diagram of the steps that take place. + +### Sequence Diagram + +> Keep in mind that the entire sequence below is duplicated for each instance of our extension. +> They all work together to "crash check" on behalf of the other crashed extension instance. + +`Crash Service`: The high level "service" that starts the heartbeats and crash checks + +`Heartbeat`: Sends heartbeats which signal that the extension is still running and has not crashed + +`Crash Checker`: Observes the heartbeats, reporting a telemetry event if a crash is detected + +`File System State`: The user's file system where we store the heartbeat files from each extension instance + +```mermaid +%%{init: {'theme':'default'}}%% +sequenceDiagram + autonumber + + participant VSC as VS Code + participant Service as Crash Service + participant Checker as Crash Checker + participant Heartbeat as Heartbeat + participant State as File System State + participant Telemetry as Telemetry + + rect rgb(121, 210, 121) + alt Extension Startup + VSC ->> Service: activate() - Start Service + + Service ->> Heartbeat: Start Heartbeats + Heartbeat ->> State: Send Initial Heartbeat
(in a folder add a unique file w/ timestamp) + rect rgb(64, 191, 64) + par every N minutes + Heartbeat ->> State: Send Heartbeat
(overwrite the unique file w/ new timestamp) + end + end + + Service ->> Checker: Start Crash Checking + rect rgb(64, 191, 64) + par every N*2 minutes + Checker ->> Checker: If computer went to sleep, skip this iteration (gives time for a heartbeat) + Checker ->> State: Request all heartbeat timestamps (readdir all heartbeat files) + State ->> Checker: Receive all heartbeat timestamps + loop for each crashed extension (it's timestamp >= N*2 minutes) + Checker ->> State: Delete heartbeat file + Checker ->> Telemetry: Send metric representing a crash: session_end + end + end + end + end + end + + rect rgb(255, 128, 128) + alt Graceful Shutdown + VSC ->> Service: deactivate() - Stop Service + Service ->> Checker: Stop + Service ->> Heartbeat: Stop + Heartbeat ->> State: Delete timestamp file
(This is missed when a crash happens) + end + end + +``` diff --git a/docs/telemetry.md b/docs/telemetry.md index 0554722b6a6..7750750c967 100644 --- a/docs/telemetry.md +++ b/docs/telemetry.md @@ -142,20 +142,23 @@ Finally, if `setupStep2()` was the thing that failed we would see a metric like: ## Adding a "Stack Trace" to your metric -### Problem +When errors are thrown we do not attach the stack trace in telemetry. We only know about the error itself, but +not the path it took to get there. We sometimes need this stack trace to debug, and only have telemetry to get insight on what happened since we do not have access to logs. + +### Scenario Common example: _"I have a function, `thisFailsSometimes()` that is called in multiple places. The function sometimes fails, I know from telemetry, but I do not know if it is failing when it is a specific caller. If I knew the call stack/trace that it took to call my function that would help me debug."_ ```typescript -function outerA() { +function runsSuccessfully() { thisFailsSometimes(1) // this succeeds } -function outerB() { +function thisThrows() { thisFailsSometimes(0) // this fails } -function thisFailsSometimes(num: number) { +function failsDependingOnInput(num: number) { return telemetry.my_Metric.run(() => { if (number === 0) { throw Error('Cannot be 0') @@ -167,31 +170,61 @@ function thisFailsSometimes(num: number) { ### Solution -Add a value to `function` in the options of a `run()`. This will result in a stack of functions identifiers that were previously called -before `thisFailsSometimes()` was run. You can then retrieve the stack in the `run()` of your final metric using `getFunctionStack()`. +On class methods, use the `@withTelemetryContext()` decorator to add context to the execution. Depending on the args set, it provides features like emitting the result, or adding it's context to errors. + +> NOTE: Decorators are currently only supported for methods and not functions + +```typescript +class MyClass { + @withTelemetryContext({ name: 'runsSuccessfully', class: 'MyClass' }) + public runsSuccessfully() { + failsDependingOnInput(1) + } + + @withTelemetryContext({ name: 'thisThrows', class: 'MyClass', errorCtx: true }) + public thisThrows() { + failsDependingOnInput(0) + } + + @withTelemetryContext({ name: 'failsDependingOnInput' class: 'MyClass', emit: true, errorCtx: true}) + private failsDependingOnInput(num: number) { + if (number === 0) { + throw Error('Cannot be 0') + } + ... + } +} + +// Results in a metric: { source: 'MyClass#thisThrows,failsDependingOnInput', result: 'Failed' } +// Results in an error that has context about the methods that lead up to it. +new MyClass().thisThrows() +``` + +Separately if you must use a function, add a value to `function` in the options of a `run()`. This will result in a stack of functions identifiers that were previously called +before `failsDependingOnInput()` was run. You can then retrieve the stack in the `run()` of your final metric using `getFunctionStack()`. ```typescript -function outerA() { - telemetry.my_Metric.run(() => thisFailsSometimes(1), { functionId: { name: 'outerA' }}) +function runsSuccessfully() { + telemetry.my_Metric.run(() => failsDependingOnInput(1), { functionId: { name: 'runsSuccessfully' }}) } -function outerB() { - telemetry.my_Metric.run(() => thisFailsSometimes(0), { functionId: { source: 'outerB' }}) +function thisThrows() { + telemetry.my_Metric.run(() => failsDependingOnInput(0), { functionId: { source: 'thisThrows' }}) } -function thisFailsSometimes(num: number) { +function failsDependingOnInput(num: number) { return telemetry.my_Metric.run(() => { telemetry.record({ theCallStack: asStringifiedStack(telemetry.getFunctionStack())}) if (number === 0) { throw Error('Cannot be 0') } ... - }, { functionId: { name: 'thisFailsSometimes' }}) + }, { functionId: { name: 'failsDependingOnInput' }}) } -// Results in a metric: { theCallStack: 'outerB:thisFailsSometimes', result: 'Failed' } -// { theCallStack: 'outerB:thisFailsSometimes' } implies 'outerB' was run first, then 'thisFailsSometimes'. See docstrings for more info. -outerB() +// Results in a metric: { theCallStack: 'thisThrows:failsDependingOnInput', result: 'Failed' } +// { theCallStack: 'thisThrows:failsDependingOnInput' } implies 'thisThrows' was run first, then 'failsDependingOnInput'. See docstrings for more info. +thisThrows() ``` ### Important Notes @@ -216,25 +249,6 @@ outerB() c() // result: 'a:c', note that 'b' is not included ``` -- If you are using `run()` with a class method, you can also add the class to the entry for more context - - ```typescript - class A { - a() { - return telemetry.my_Metric.run(() => this.b(), { functionId: { name: 'a', class: 'A' } }) - } - - b() { - return telemetry.my_Metric.run(() => asStringifiedStack(telemetry.getFunctionStack()), { - functionId: { name: 'b', class: 'A' }, - }) - } - } - - const inst = new A() - inst.a() // 'A#a,b' - ``` - - If you do not want your `run()` to emit telemetry, set `emit: false` in the options ```typescript diff --git a/docs/vscode_behaviors.md b/docs/vscode_behaviors.md new file mode 100644 index 00000000000..6d2dca2546a --- /dev/null +++ b/docs/vscode_behaviors.md @@ -0,0 +1,31 @@ +# VS Code Behaviors + +Many VS Code behavoirs for certain APIs or user interactions with the IDE are not clearly documented, +or documented at all. Please add any findings to this document. + +## `deactivate()` - extension shutdown + +This method is defined as part of the VS Code extension API, and is run on a **graceful** shutdown +for each extension. + +- Our extension and its `deactivate()` function are in the Extension Host process [1] +- The Extension Host process has at most 5 seconds to shut down, after which it will exit. [1] +- The vscode API will be unreliable at deactivation time. So certain VSC APIs like the filesystem may not work. [1] + - The VSC Filesystem API has been confirmed to not work +- In `Run & Debug` mode, closing the Debug IDE instance behaves differently depending on how it is closed + - The regular close button in the Debug IDE instance results in a graceful shutdown + - The red square in the root IDE instance to stop the debugging session results on a non-graceful shutdown, meaning `deactivate()` is not run. +- `Reload Window` triggers `deactivate()` + +Sources: + +- [[1]](https://github.com/Microsoft/vscode/issues/47881#issuecomment-381910587) +- [[2]](https://github.com/microsoft/vscode/issues/122825#issuecomment-814218149) + +## State (`globalState`, `Memento`) + +TODO: + +- How it behaves between remote (ssh) and local +- How it is not completely reliable. Reads/writes have no guarantee to work (though we are fine in most cases) +- How it can break as observed with crash monitoring work. At a certain point writes were seemingly succeeding, but did not actually propagate to all IDE instances. diff --git a/package-lock.json b/package-lock.json index cba78c3f2b5..8a549622f37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,15 +15,18 @@ "plugins/*" ], "dependencies": { + "@types/node": "^22.7.5", "vscode-nls": "^5.2.0", "vscode-nls-dev": "^4.0.4" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.258", + "@aws-toolkits/telemetry": "^1.0.289", "@playwright/browser-chromium": "^1.43.1", + "@stylistic/eslint-plugin": "^2.11.0", + "@types/he": "^1.2.3", "@types/vscode": "^1.68.0", "@types/vscode-webview": "^1.57.1", - "@types/webpack-env": "^1.18.1", + "@types/webpack-env": "^1.18.5", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", "@vscode/codicons": "^0.0.33", @@ -38,14 +41,14 @@ "eslint-plugin-security-node": "^1.1.4", "eslint-plugin-unicorn": "^54.0.0", "husky": "^9.0.7", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", "ts-node": "^10.9.1", "typescript": "^5.0.4", - "webpack": "^5.83.0", + "webpack": "^5.95.0", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1", + "webpack-dev-server": "^4.15.2", "webpack-merge": "^5.10.0" } }, @@ -57,848 +60,1136 @@ "node": ">=0.10.0" } }, + "node_modules/@amzn/amazon-q-developer-streaming-client": { + "resolved": "src.gen/@amzn/amazon-q-developer-streaming-client", + "link": true + }, "node_modules/@amzn/codewhisperer-streaming": { "resolved": "src.gen/@amzn/codewhisperer-streaming", "link": true }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.637.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.637.0", - "@aws-sdk/client-sts": "3.637.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.637.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.637.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/sha256-browser": { + "node_modules/@aws-crypto/crc32": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", + "node_modules/@aws-crypto/crc32/node_modules/@aws-sdk/types": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.664.0.tgz", + "integrity": "sha512-+GtXktvVgpreM2b+NJL9OqZGsOzHwlCUrO8jgQUvH/yA6Kd8QO2YFhQCp0C9sSzTteZJVqGBu8E0CQurxJHPbw==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", + "node_modules/@aws-crypto/crc32/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/util": { + "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", + "node_modules/@aws-crypto/sha256-browser/node_modules/@aws-sdk/types": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.664.0.tgz", + "integrity": "sha512-+GtXktvVgpreM2b+NJL9OqZGsOzHwlCUrO8jgQUvH/yA6Kd8QO2YFhQCp0C9sSzTteZJVqGBu8E0CQurxJHPbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/abort-controller": { - "version": "3.1.1", + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/config-resolver": { - "version": "3.0.5", + "node_modules/@aws-crypto/sha256-js/node_modules/@aws-sdk/types": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.664.0.tgz", + "integrity": "sha512-+GtXktvVgpreM2b+NJL9OqZGsOzHwlCUrO8jgQUvH/yA6Kd8QO2YFhQCp0C9sSzTteZJVqGBu8E0CQurxJHPbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/credential-provider-imds": { - "version": "3.2.0", + "node_modules/@aws-crypto/sha256-js/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/hash-node": { - "version": "3.0.3", + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", + "node_modules/@aws-crypto/util/node_modules/@aws-sdk/types": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.664.0.tgz", + "integrity": "sha512-+GtXktvVgpreM2b+NJL9OqZGsOzHwlCUrO8jgQUvH/yA6Kd8QO2YFhQCp0C9sSzTteZJVqGBu8E0CQurxJHPbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/invalid-dependency": { - "version": "3.0.3", + "node_modules/@aws-crypto/util/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.682.0.tgz", + "integrity": "sha512-RJVzgm9Q15yWnU4mFiHEO1M7k8d5ARgDhGkKSz5sE10dzG5vKockqkhgvwBgmwiAgkfnSkI/Bzx3/baBMm9BVQ==", "dependencies": { - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.682.0", + "@aws-sdk/client-sts": "3.682.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.682.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.6", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-content-length": { - "version": "3.0.5", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/client-sso": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.682.0.tgz", + "integrity": "sha512-PYH9RFUMYLFl66HSBq4tIx6fHViMLkhJHTYJoJONpBs+Td+NwVJ895AdLtDsBIhMS0YseCbPpuyjUCJgsUrwUw==", "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-endpoint": { - "version": "3.1.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.682.0.tgz", + "integrity": "sha512-ZPZ7Y/r/w3nx/xpPzGSqSQsB090Xk5aZZOH+WBhTDn/pBEuim09BYXCLzvvxb7R7NnuoQdrTJiwimdJAhHl7ZQ==", "dependencies": { - "@smithy/middleware-serde": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-middleware": "^3.0.3", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.682.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.682.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-retry": { - "version": "3.0.15", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/client-sts": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.682.0.tgz", + "integrity": "sha512-xKuo4HksZ+F8m9DOfx/ZuWNhaPuqZFPwwy0xqcBT6sWH7OAuBjv/fnpOTzyQhpVTWddlf+ECtMAMrxjxuOExGQ==", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/service-error-classification": "^3.0.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.682.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.682.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-serde": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/core": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.679.0.tgz", + "integrity": "sha512-CS6PWGX8l4v/xyvX8RtXnBisdCa5+URzKd0L6GvHChype9qKUVxO/Gg6N/y43Hvg7MNWJt9FBPNWIxUB+byJwg==", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.679.0", + "@smithy/core": "^2.4.8", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/property-provider": "^3.1.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/signature-v4": "^4.2.0", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/util-middleware": "^3.0.7", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-stack": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.679.0.tgz", + "integrity": "sha512-EdlTYbzMm3G7VUNAMxr9S1nC1qUNqhKlAxFU8E7cKsAe8Bp29CD5HAs3POc56AVo9GC4yRIS+/mtlZSmrckzUA==", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-config-provider": { - "version": "3.1.4", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.679.0.tgz", + "integrity": "sha512-ZoKLubW5DqqV1/2a3TSn+9sSKg0T8SsYMt1JeirnuLJF0mCoYFUaWMyvxxKuxPoqvUsaycxKru4GkpJ10ltNBw==", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/util-stream": "^3.1.9", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-http-handler": { - "version": "3.1.4", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.682.0.tgz", + "integrity": "sha512-6eqWeHdK6EegAxqDdiCi215nT3QZPwukgWAYuVxNfJ/5m0/P7fAzF+D5kKVgByUvGJEbq/FEL8Fw7OBe64AA+g==", "dependencies": { - "@smithy/abort-controller": "^3.1.1", - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-env": "3.679.0", + "@aws-sdk/credential-provider-http": "3.679.0", + "@aws-sdk/credential-provider-process": "3.679.0", + "@aws-sdk/credential-provider-sso": "3.682.0", + "@aws-sdk/credential-provider-web-identity": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/credential-provider-imds": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.682.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/property-provider": { - "version": "3.1.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.682.0.tgz", + "integrity": "sha512-HSmDqZcBVZrTctHCT9m++vdlDfJ1ARI218qmZa+TZzzOFNpKWy6QyHMEra45GB9GnkkMmV6unoDSPMuN0AqcMg==", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/credential-provider-env": "3.679.0", + "@aws-sdk/credential-provider-http": "3.679.0", + "@aws-sdk/credential-provider-ini": "3.682.0", + "@aws-sdk/credential-provider-process": "3.679.0", + "@aws-sdk/credential-provider-sso": "3.682.0", + "@aws-sdk/credential-provider-web-identity": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/credential-provider-imds": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/protocol-http": { - "version": "4.1.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.679.0.tgz", + "integrity": "sha512-u/p4TV8kQ0zJWDdZD4+vdQFTMhkDEJFws040Gm113VHa/Xo1SYOjbpvqeuFoz6VmM0bLvoOWjxB9MxnSQbwKpQ==", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-builder": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.682.0.tgz", + "integrity": "sha512-h7IH1VsWgV6YAJSWWV6y8uaRjGqLY3iBpGZlXuTH/c236NMLaNv+WqCBLeBxkFGUb2WeQ+FUPEJDCD69rgLIkg==", "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-uri-escape": "^3.0.0", + "@aws-sdk/client-sso": "3.682.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/token-providers": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-parser": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.679.0.tgz", + "integrity": "sha512-a74tLccVznXCaBefWPSysUcLXYJiSkeUmQGtalNgJ1vGkE36W5l/8czFiiowdWdKWz7+x6xf0w+Kjkjlj42Ung==", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.679.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/service-error-classification": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.679.0.tgz", + "integrity": "sha512-y176HuQ8JRY3hGX8rQzHDSbCl9P5Ny9l16z4xmaiLo+Qfte7ee4Yr3yaAKd7GFoJ3/Mhud2XZ37fR015MfYl2w==", "dependencies": { - "@smithy/types": "^3.3.0" + "@aws-sdk/types": "3.679.0", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.4", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/middleware-logger": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.679.0.tgz", + "integrity": "sha512-0vet8InEj7nvIvGKk+ch7bEF5SyZ7Us9U7YTEgXPrBNStKeRUsgwRm0ijPWWd0a3oz2okaEwXsFl7G/vI0XiEA==", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.679.0", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/smithy-client": { - "version": "3.2.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.679.0.tgz", + "integrity": "sha512-sQoAZFsQiW/LL3DfKMYwBoGjYDEnMbA9WslWN8xneCmBAwKo6IcSksvYs23PP8XMIoBGe2I2J9BSr654XWygTQ==", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.1.3", + "@aws-sdk/types": "3.679.0", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/types": { - "version": "3.3.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.682.0.tgz", + "integrity": "sha512-7TyvYR9HdGH1/Nq0eeApUTM4izB6rExiw87khVYuJwZHr6FmvIL1FsOVFro/4WlXa0lg4LiYOm/8H8dHv+fXTg==", "dependencies": { + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@smithy/core": "^2.4.8", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/url-parser": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.679.0.tgz", + "integrity": "sha512-Ybx54P8Tg6KKq5ck7uwdjiKif7n/8g1x+V0V9uTjBjRWqaIgiqzXwKWoPj6NCNkE7tJNtqI4JrNxp/3S3HvmRw==", "dependencies": { - "@smithy/querystring-parser": "^3.0.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.679.0", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.7", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-base64": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/token-providers": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.679.0.tgz", + "integrity": "sha512-1/+Zso/x2jqgutKixYFQEGli0FELTgah6bm7aB+m2FAWH4Hz7+iMUsazg6nSWm714sG9G3h5u42Dmpvi9X6/hA==", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.679.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/types": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.679.0.tgz", + "integrity": "sha512-NwVq8YvInxQdJ47+zz4fH3BRRLC6lL+WLkvr242PVBbUOLRyK/lkwHlfiKUoeVIMyK5NF+up6TRg71t/8Bny6Q==", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-body-length-browser": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/util-endpoints": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.679.0.tgz", + "integrity": "sha512-YL6s4Y/1zC45OvddvgE139fjeWSKKPgLlnfrvhVL7alNyY9n7beR4uhoDpNrt5mI6sn9qiBF17790o+xLAXjjg==", "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/types": "^3.5.0", + "@smithy/util-endpoints": "^2.1.3", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-body-length-node": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.679.0.tgz", + "integrity": "sha512-CusSm2bTBG1kFypcsqU8COhnYc6zltobsqs3nRrvYqYaOqtMnuE46K4XTWpnzKgwDejgZGOE+WYyprtAxrPvmQ==", + "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/types": "^3.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cloudformation/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.682.0.tgz", + "integrity": "sha512-so5s+j0gPoTS0HM4HPL+G0ajk0T6cQAg8JXzRgvyiQAxqie+zGCZAV3VuVeMNWMVbzsgZl0pYZaatPFTLG/AxA==", "dependencies": { + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/types": "3.679.0", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-config-provider": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/abort-controller": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", + "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", "dependencies": { + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.15", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz", + "integrity": "sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A==", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "bowser": "^2.11.0", + "@smithy/protocol-http": "^4.1.4", + "@smithy/querystring-builder": "^3.0.7", + "@smithy/types": "^3.5.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 10.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.15", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "dependencies": { - "@smithy/config-resolver": "^3.0.5", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-hex-encoding": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/middleware-endpoint": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", + "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-middleware": "^3.0.8", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-middleware": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/middleware-retry": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz", + "integrity": "sha512-m1F70cPaMBML4HiTgCw5I+jFNtjgz5z5UdGnUbG37vw6kh4UvizFYjqJGHvicfgKMkDL6mXwyPp5mhZg02g5sg==", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@smithy/node-config-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.5", + "@smithy/service-error-classification": "^3.0.8", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-retry": "^3.0.8", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-retry": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/middleware-serde": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", + "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", "dependencies": { - "@smithy/service-error-classification": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-stream": { - "version": "3.1.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/middleware-stack": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", + "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", "dependencies": { - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/node-http-handler": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", + "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", "dependencies": { + "@smithy/abort-controller": "^3.1.6", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda": { - "version": "3.637.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/querystring-builder": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", + "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.637.0", - "@aws-sdk/client-sts": "3.637.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.637.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.637.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/eventstream-serde-browser": "^3.0.6", - "@smithy/eventstream-serde-config-resolver": "^3.0.3", - "@smithy/eventstream-serde-node": "^3.0.5", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-stream": "^3.1.3", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.2", + "@smithy/types": "^3.6.0", + "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/querystring-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", + "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/service-error-classification": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz", + "integrity": "sha512-uEC/kCCFto83bz5ZzapcrgGqHOh/0r69sZ2ZuHlgoD5kYgXJEThCoTuw/y1Ub3cE7aaKdznb+jD9xRPIfIwD7g==", "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" + "@smithy/types": "^3.6.0" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/smithy-client": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", + "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", + "@smithy/core": "^2.5.1", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-stream": "^3.2.1", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", "dependencies": { "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/util": { - "version": "5.2.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/url-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", + "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", + "@smithy/querystring-parser": "^3.0.8", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/types": { - "version": "3.609.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/abort-controller": { - "version": "3.1.1", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "dependencies": { - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/config-resolver": { - "version": "3.0.5", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/credential-provider-imds": { - "version": "3.2.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/util-retry": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz", + "integrity": "sha512-TCEhLnY581YJ+g1x0hapPz13JFqzmh/pMWL2KEFASC51qCfw3+Y47MrTmea4bUE5vsdxQ4F6/KFbUeSz22Q1ow==", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", + "@smithy/service-error-classification": "^3.0.8", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-codec": { - "version": "3.1.2", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/util-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", + "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^3.3.0", + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-hex-encoding": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-serde-browser": { - "version": "3.0.6", - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.5", - "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", + "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-serde-node": { - "version": "3.0.5", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.5", - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/eventstream-serde-universal": { - "version": "3.0.5", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-cloudformation/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "dependencies": { - "@smithy/eventstream-codec": "^3.1.2", - "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", + "node_modules/@aws-sdk/client-cloudwatch-logs": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.682.0.tgz", + "integrity": "sha512-fodsKdamwVpc9wLRrly4Ccur9TZdfDjrlDY2V0pZT+B4XS9PKhPaQ/8JY4/5ysJoUCe3NxFEpQxkediKEbu2uQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.682.0", + "@aws-sdk/client-sts": "3.682.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.682.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/eventstream-serde-browser": "^3.0.10", + "@smithy/eventstream-serde-config-resolver": "^3.0.7", + "@smithy/eventstream-serde-node": "^3.0.9", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/hash-node": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.682.0.tgz", + "integrity": "sha512-PYH9RFUMYLFl66HSBq4tIx6fHViMLkhJHTYJoJONpBs+Td+NwVJ895AdLtDsBIhMS0YseCbPpuyjUCJgsUrwUw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-buffer-from": "^3.0.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -906,262 +1197,465 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.682.0.tgz", + "integrity": "sha512-ZPZ7Y/r/w3nx/xpPzGSqSQsB090Xk5aZZOH+WBhTDn/pBEuim09BYXCLzvvxb7R7NnuoQdrTJiwimdJAhHl7ZQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.682.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.682.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/invalid-dependency": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sts": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.682.0.tgz", + "integrity": "sha512-xKuo4HksZ+F8m9DOfx/ZuWNhaPuqZFPwwy0xqcBT6sWH7OAuBjv/fnpOTzyQhpVTWddlf+ECtMAMrxjxuOExGQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.682.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.682.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/core": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.679.0.tgz", + "integrity": "sha512-CS6PWGX8l4v/xyvX8RtXnBisdCa5+URzKd0L6GvHChype9qKUVxO/Gg6N/y43Hvg7MNWJt9FBPNWIxUB+byJwg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/core": "^2.4.8", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/property-provider": "^3.1.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/signature-v4": "^4.2.0", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/util-middleware": "^3.0.7", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-content-length": { - "version": "3.0.5", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.679.0.tgz", + "integrity": "sha512-EdlTYbzMm3G7VUNAMxr9S1nC1qUNqhKlAxFU8E7cKsAe8Bp29CD5HAs3POc56AVo9GC4yRIS+/mtlZSmrckzUA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-endpoint": { - "version": "3.1.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.679.0.tgz", + "integrity": "sha512-ZoKLubW5DqqV1/2a3TSn+9sSKg0T8SsYMt1JeirnuLJF0mCoYFUaWMyvxxKuxPoqvUsaycxKru4GkpJ10ltNBw==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-middleware": "^3.0.3", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/util-stream": "^3.1.9", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-retry": { - "version": "3.0.15", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.682.0.tgz", + "integrity": "sha512-6eqWeHdK6EegAxqDdiCi215nT3QZPwukgWAYuVxNfJ/5m0/P7fAzF+D5kKVgByUvGJEbq/FEL8Fw7OBe64AA+g==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/service-error-classification": "^3.0.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-env": "3.679.0", + "@aws-sdk/credential-provider-http": "3.679.0", + "@aws-sdk/credential-provider-process": "3.679.0", + "@aws-sdk/credential-provider-sso": "3.682.0", + "@aws-sdk/credential-provider-web-identity": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/credential-provider-imds": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.682.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-serde": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.682.0.tgz", + "integrity": "sha512-HSmDqZcBVZrTctHCT9m++vdlDfJ1ARI218qmZa+TZzzOFNpKWy6QyHMEra45GB9GnkkMmV6unoDSPMuN0AqcMg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/credential-provider-env": "3.679.0", + "@aws-sdk/credential-provider-http": "3.679.0", + "@aws-sdk/credential-provider-ini": "3.682.0", + "@aws-sdk/credential-provider-process": "3.679.0", + "@aws-sdk/credential-provider-sso": "3.682.0", + "@aws-sdk/credential-provider-web-identity": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/credential-provider-imds": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-stack": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.679.0.tgz", + "integrity": "sha512-u/p4TV8kQ0zJWDdZD4+vdQFTMhkDEJFws040Gm113VHa/Xo1SYOjbpvqeuFoz6VmM0bLvoOWjxB9MxnSQbwKpQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/node-config-provider": { - "version": "3.1.4", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.682.0.tgz", + "integrity": "sha512-h7IH1VsWgV6YAJSWWV6y8uaRjGqLY3iBpGZlXuTH/c236NMLaNv+WqCBLeBxkFGUb2WeQ+FUPEJDCD69rgLIkg==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/client-sso": "3.682.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/token-providers": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/node-http-handler": { - "version": "3.1.4", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.679.0.tgz", + "integrity": "sha512-a74tLccVznXCaBefWPSysUcLXYJiSkeUmQGtalNgJ1vGkE36W5l/8czFiiowdWdKWz7+x6xf0w+Kjkjlj42Ung==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.1", - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.679.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/property-provider": { - "version": "3.1.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.679.0.tgz", + "integrity": "sha512-y176HuQ8JRY3hGX8rQzHDSbCl9P5Ny9l16z4xmaiLo+Qfte7ee4Yr3yaAKd7GFoJ3/Mhud2XZ37fR015MfYl2w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.679.0", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/protocol-http": { - "version": "4.1.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-logger": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.679.0.tgz", + "integrity": "sha512-0vet8InEj7nvIvGKk+ch7bEF5SyZ7Us9U7YTEgXPrBNStKeRUsgwRm0ijPWWd0a3oz2okaEwXsFl7G/vI0XiEA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.679.0", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/querystring-builder": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.679.0.tgz", + "integrity": "sha512-sQoAZFsQiW/LL3DfKMYwBoGjYDEnMbA9WslWN8xneCmBAwKo6IcSksvYs23PP8XMIoBGe2I2J9BSr654XWygTQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-uri-escape": "^3.0.0", + "@aws-sdk/types": "3.679.0", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/querystring-parser": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.682.0.tgz", + "integrity": "sha512-7TyvYR9HdGH1/Nq0eeApUTM4izB6rExiw87khVYuJwZHr6FmvIL1FsOVFro/4WlXa0lg4LiYOm/8H8dHv+fXTg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@smithy/core": "^2.4.8", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/service-error-classification": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.679.0.tgz", + "integrity": "sha512-Ybx54P8Tg6KKq5ck7uwdjiKif7n/8g1x+V0V9uTjBjRWqaIgiqzXwKWoPj6NCNkE7tJNtqI4JrNxp/3S3HvmRw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0" + "@aws-sdk/types": "3.679.0", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.7", + "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.4", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/token-providers": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.679.0.tgz", + "integrity": "sha512-1/+Zso/x2jqgutKixYFQEGli0FELTgah6bm7aB+m2FAWH4Hz7+iMUsazg6nSWm714sG9G3h5u42Dmpvi9X6/hA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.679.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/smithy-client": { - "version": "3.2.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/types": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.679.0.tgz", + "integrity": "sha512-NwVq8YvInxQdJ47+zz4fH3BRRLC6lL+WLkvr242PVBbUOLRyK/lkwHlfiKUoeVIMyK5NF+up6TRg71t/8Bny6Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.1.3", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-endpoints": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.679.0.tgz", + "integrity": "sha512-YL6s4Y/1zC45OvddvgE139fjeWSKKPgLlnfrvhVL7alNyY9n7beR4uhoDpNrt5mI6sn9qiBF17790o+xLAXjjg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/types": "^3.5.0", + "@smithy/util-endpoints": "^2.1.3", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/url-parser": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.679.0.tgz", + "integrity": "sha512-CusSm2bTBG1kFypcsqU8COhnYc6zltobsqs3nRrvYqYaOqtMnuE46K4XTWpnzKgwDejgZGOE+WYyprtAxrPvmQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^3.0.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.679.0", + "@smithy/types": "^3.5.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-base64": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.682.0.tgz", + "integrity": "sha512-so5s+j0gPoTS0HM4HPL+G0ajk0T6cQAg8JXzRgvyiQAxqie+zGCZAV3VuVeMNWMVbzsgZl0pYZaatPFTLG/AxA==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/types": "3.679.0", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/abort-controller": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", + "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-body-length-browser": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz", + "integrity": "sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A==", "license": "Apache-2.0", "dependencies": { + "@smithy/protocol-http": "^4.1.4", + "@smithy/querystring-builder": "^3.0.7", + "@smithy/types": "^3.5.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-body-length-node": { + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1170,394 +1664,428 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-config-provider": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-endpoint": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", + "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-middleware": "^3.0.8", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.15", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-retry": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz", + "integrity": "sha512-m1F70cPaMBML4HiTgCw5I+jFNtjgz5z5UdGnUbG37vw6kh4UvizFYjqJGHvicfgKMkDL6mXwyPp5mhZg02g5sg==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@smithy/node-config-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.5", + "@smithy/service-error-classification": "^3.0.8", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-retry": "^3.0.8", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.15", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-serde": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", + "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^3.0.5", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-hex-encoding": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-stack": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", + "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", "license": "Apache-2.0", "dependencies": { + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-middleware": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-retry": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/node-http-handler": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", + "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/abort-controller": "^3.1.6", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-stream": { - "version": "3.1.3", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/querystring-builder": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", + "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", "license": "Apache-2.0", "dependencies": { + "@smithy/types": "^3.6.0", + "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-utf8": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/querystring-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", + "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/service-error-classification": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz", + "integrity": "sha512-uEC/kCCFto83bz5ZzapcrgGqHOh/0r69sZ2ZuHlgoD5kYgXJEThCoTuw/y1Ub3cE7aaKdznb+jD9xRPIfIwD7g==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/types": "^3.6.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.637.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/smithy-client": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", + "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.637.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@smithy/core": "^2.5.1", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-stream": "^3.2.1", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.637.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.637.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.637.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.637.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/url-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", + "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", + "@smithy/querystring-parser": "^3.0.8", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-crypto/util": { - "version": "5.2.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-retry": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz", + "integrity": "sha512-TCEhLnY581YJ+g1x0hapPz13JFqzmh/pMWL2KEFASC51qCfw3+Y47MrTmea4bUE5vsdxQ4F6/KFbUeSz22Q1ow==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@smithy/service-error-classification": "^3.0.8", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", + "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/abort-controller": { - "version": "3.1.1", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", + "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/config-resolver": { - "version": "3.0.5", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/credential-provider-imds": { - "version": "3.2.0", + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/hash-node": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/abort-controller": { + "version": "3.1.1", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/invalid-dependency": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.4", "license": "Apache-2.0", "dependencies": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -1567,19 +2095,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-content-length": { - "version": "3.0.5", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-endpoint": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-endpoint": { "version": "3.1.0", "license": "Apache-2.0", "dependencies": { @@ -1595,7 +2111,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-retry": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-retry": { "version": "3.0.15", "license": "Apache-2.0", "dependencies": { @@ -1613,7 +2129,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-serde": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-serde": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -1624,7 +2140,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-stack": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-stack": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -1635,7 +2151,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-config-provider": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-config-provider": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -1648,7 +2164,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-http-handler": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -1662,7 +2178,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/property-provider": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/property-provider": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -1673,7 +2189,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/protocol-http": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/protocol-http": { "version": "4.1.0", "license": "Apache-2.0", "dependencies": { @@ -1684,7 +2200,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/querystring-builder": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-builder": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -1696,7 +2212,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/querystring-parser": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -1707,7 +2223,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/service-error-classification": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/service-error-classification": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -1717,7 +2233,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/shared-ini-file-loader": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -1728,7 +2244,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/smithy-client": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/smithy-client": { "version": "3.2.0", "license": "Apache-2.0", "dependencies": { @@ -1743,7 +2259,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/types": { "version": "3.3.0", "license": "Apache-2.0", "dependencies": { @@ -1753,7 +2269,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/url-parser": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/url-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -1762,7 +2278,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-base64": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-base64": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -1774,7 +2290,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -1785,14 +2301,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-body-length-browser": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-body-length-node": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-hex-encoding": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -1802,118 +2311,68 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-config-provider": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-middleware": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.15", + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-retry": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", + "@smithy/service-error-classification": "^3.0.3", "@smithy/types": "^3.3.0", - "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.15", + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-stream": { + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^3.0.5", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-hex-encoding": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-middleware": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-retry": { - "version": "3.0.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^3.0.3", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-stream": { - "version": "3.1.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-utf8": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -1924,7 +2383,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -1935,70 +2394,62 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", + "node_modules/@aws-sdk/client-lambda": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-crypto/util": { - "version": "5.2.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/types": { "version": "3.609.0", "license": "Apache-2.0", "dependencies": { @@ -2009,7 +2460,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/abort-controller": { "version": "3.1.1", "license": "Apache-2.0", "dependencies": { @@ -2020,35 +2471,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/config-resolver": { - "version": "3.0.5", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/credential-provider-imds": { - "version": "3.2.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/fetch-http-handler": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/fetch-http-handler": { "version": "3.2.4", "license": "Apache-2.0", "dependencies": { @@ -2059,39 +2482,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/hash-node": { - "version": "3.0.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/invalid-dependency": { - "version": "3.0.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2101,19 +2492,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-content-length": { - "version": "3.0.5", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-endpoint": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-endpoint": { "version": "3.1.0", "license": "Apache-2.0", "dependencies": { @@ -2129,7 +2508,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-retry": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-retry": { "version": "3.0.15", "license": "Apache-2.0", "dependencies": { @@ -2147,7 +2526,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-serde": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-serde": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2158,7 +2537,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-stack": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/middleware-stack": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2169,7 +2548,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-config-provider": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/node-config-provider": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -2182,7 +2561,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/node-http-handler": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -2196,7 +2575,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/property-provider": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/property-provider": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -2207,7 +2586,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/protocol-http": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/protocol-http": { "version": "4.1.0", "license": "Apache-2.0", "dependencies": { @@ -2218,7 +2597,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/querystring-builder": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/querystring-builder": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2230,7 +2609,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/querystring-parser": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/querystring-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2241,7 +2620,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/service-error-classification": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/service-error-classification": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2251,7 +2630,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/shared-ini-file-loader": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -2262,7 +2641,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/smithy-client": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/smithy-client": { "version": "3.2.0", "license": "Apache-2.0", "dependencies": { @@ -2277,7 +2656,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/types": { "version": "3.3.0", "license": "Apache-2.0", "dependencies": { @@ -2287,7 +2666,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/url-parser": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/url-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2296,7 +2675,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-base64": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-base64": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2308,7 +2687,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2319,64 +2698,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-body-length-browser": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-body-length-node": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-config-provider": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.15", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.15", - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^3.0.5", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-hex-encoding": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-hex-encoding": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2386,7 +2708,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-middleware": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-middleware": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2397,7 +2719,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-retry": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-retry": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2409,7 +2731,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-stream": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-stream": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -2426,7 +2748,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2437,7 +2759,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-uri-escape": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-uri-escape": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2447,7 +2769,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-utf8": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2458,7 +2780,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2469,15 +2791,13 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts": { + "node_modules/@aws-sdk/client-sso": { "version": "3.637.0", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.637.0", "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.637.0", "@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-recursion-detection": "3.620.0", @@ -2518,70 +2838,58 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-crypto/util": { - "version": "5.2.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" }, - "engines": { - "node": ">=14.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/types": { "version": "3.609.0", "license": "Apache-2.0", "dependencies": { @@ -2592,7 +2900,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/abort-controller": { "version": "3.1.1", "license": "Apache-2.0", "dependencies": { @@ -2603,35 +2911,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/config-resolver": { - "version": "3.0.5", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/credential-provider-imds": { - "version": "3.2.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/fetch-http-handler": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/fetch-http-handler": { "version": "3.2.4", "license": "Apache-2.0", "dependencies": { @@ -2642,39 +2922,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/hash-node": { - "version": "3.0.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/invalid-dependency": { - "version": "3.0.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2684,19 +2932,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-content-length": { - "version": "3.0.5", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-endpoint": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-endpoint": { "version": "3.1.0", "license": "Apache-2.0", "dependencies": { @@ -2712,7 +2948,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-retry": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-retry": { "version": "3.0.15", "license": "Apache-2.0", "dependencies": { @@ -2730,7 +2966,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-serde": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-serde": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2741,7 +2977,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-stack": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-stack": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2752,7 +2988,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/node-config-provider": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-config-provider": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -2765,7 +3001,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -2779,7 +3015,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/property-provider": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/property-provider": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -2790,7 +3026,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/protocol-http": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/protocol-http": { "version": "4.1.0", "license": "Apache-2.0", "dependencies": { @@ -2801,7 +3037,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/querystring-builder": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/querystring-builder": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2813,7 +3049,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/querystring-parser": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/querystring-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2824,7 +3060,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/service-error-classification": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/service-error-classification": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2834,7 +3070,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/shared-ini-file-loader": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -2845,7 +3081,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/smithy-client": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/smithy-client": { "version": "3.2.0", "license": "Apache-2.0", "dependencies": { @@ -2860,7 +3096,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { "version": "3.3.0", "license": "Apache-2.0", "dependencies": { @@ -2870,7 +3106,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/url-parser": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/url-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2879,7 +3115,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-base64": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-base64": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2891,7 +3127,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2902,64 +3138,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-body-length-browser": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-body-length-node": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-config-provider": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.15", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.15", - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^3.0.5", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-hex-encoding": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-hex-encoding": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -2969,7 +3148,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-middleware": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-middleware": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2980,7 +3159,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-retry": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-retry": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -2992,7 +3171,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-stream": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-stream": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -3009,7 +3188,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3020,7 +3199,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-uri-escape": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-uri-escape": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3030,7 +3209,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3041,7 +3220,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3052,26 +3231,18 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.635.0", + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^2.4.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/signature-v4": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/abort-controller": { "version": "3.1.1", "license": "Apache-2.0", "dependencies": { @@ -3082,7 +3253,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/fetch-http-handler": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/fetch-http-handler": { "version": "3.2.4", "license": "Apache-2.0", "dependencies": { @@ -3093,7 +3264,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3103,7 +3274,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-endpoint": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-endpoint": { "version": "3.1.0", "license": "Apache-2.0", "dependencies": { @@ -3119,7 +3290,25 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-serde": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-retry": { + "version": "3.0.15", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-serde": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3130,7 +3319,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-stack": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-stack": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3141,7 +3330,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/node-config-provider": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-config-provider": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -3154,7 +3343,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -3168,7 +3357,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/property-provider": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/property-provider": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -3179,7 +3368,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/protocol-http": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/protocol-http": { "version": "4.1.0", "license": "Apache-2.0", "dependencies": { @@ -3190,7 +3379,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/querystring-builder": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/querystring-builder": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3202,7 +3391,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/querystring-parser": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/querystring-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3213,7 +3402,17 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/shared-ini-file-loader": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/service-error-classification": { + "version": "3.0.3", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -3224,7 +3423,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/smithy-client": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/smithy-client": { "version": "3.2.0", "license": "Apache-2.0", "dependencies": { @@ -3239,7 +3438,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { "version": "3.3.0", "license": "Apache-2.0", "dependencies": { @@ -3249,7 +3448,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/url-parser": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/url-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3258,7 +3457,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/util-base64": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-base64": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3270,7 +3469,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3281,7 +3480,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/util-hex-encoding": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-hex-encoding": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3291,7 +3490,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/util-middleware": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-middleware": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3302,7 +3501,19 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/util-stream": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-retry": { + "version": "3.0.3", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-stream": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -3319,65 +3530,99 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/util-uri-escape": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-uri-escape": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.46.0", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/property-provider": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/types": { - "version": "3.46.0", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.635.0", + "node_modules/@aws-sdk/client-sts": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", "@smithy/node-http-handler": "^3.1.4", - "@smithy/property-provider": "^3.1.3", "@smithy/protocol-http": "^4.1.0", "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.1.3", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { "version": "3.609.0", "license": "Apache-2.0", "dependencies": { @@ -3388,7 +3633,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/abort-controller": { "version": "3.1.1", "license": "Apache-2.0", "dependencies": { @@ -3399,7 +3644,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/fetch-http-handler": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/fetch-http-handler": { "version": "3.2.4", "license": "Apache-2.0", "dependencies": { @@ -3410,7 +3655,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3420,7 +3665,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-endpoint": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-endpoint": { "version": "3.1.0", "license": "Apache-2.0", "dependencies": { @@ -3436,8 +3681,26 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-serde": { - "version": "3.0.3", + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-retry": { + "version": "3.0.15", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-serde": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -3447,7 +3710,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-stack": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-stack": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3458,7 +3721,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-config-provider": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/node-config-provider": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -3471,7 +3734,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/node-http-handler": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -3485,7 +3748,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/property-provider": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/property-provider": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -3496,7 +3759,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/protocol-http": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/protocol-http": { "version": "4.1.0", "license": "Apache-2.0", "dependencies": { @@ -3507,7 +3770,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/querystring-builder": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/querystring-builder": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3519,7 +3782,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/querystring-parser": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/querystring-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3530,7 +3793,17 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/shared-ini-file-loader": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/service-error-classification": { + "version": "3.0.3", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -3541,7 +3814,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/smithy-client": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/smithy-client": { "version": "3.2.0", "license": "Apache-2.0", "dependencies": { @@ -3556,7 +3829,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/types": { "version": "3.3.0", "license": "Apache-2.0", "dependencies": { @@ -3566,7 +3839,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/url-parser": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/url-parser": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3575,7 +3848,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-base64": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-base64": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3587,7 +3860,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3598,7 +3871,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-hex-encoding": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-hex-encoding": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -3608,7 +3881,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-middleware": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-middleware": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -3619,7 +3892,19 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-stream": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-retry": { + "version": "3.0.3", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-stream": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -3636,381 +3921,342 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-uri-escape": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-uri-escape": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.46.0", + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/node-config-provider": "3.46.0", - "@aws-sdk/property-provider": "3.46.0", - "@aws-sdk/types": "3.46.0", - "@aws-sdk/url-parser": "3.46.0", - "tslib": "^2.3.0" + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/node-config-provider": { - "version": "3.46.0", + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/property-provider": "3.46.0", - "@aws-sdk/shared-ini-file-loader": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/querystring-parser": { - "version": "3.46.0", + "node_modules/@aws-sdk/core": { + "version": "3.635.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/abort-controller": { + "version": "3.1.1", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/types": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.4", "license": "Apache-2.0", - "engines": { - "node": ">= 12.0.0" + "dependencies": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/url-parser": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/querystring-parser": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-endpoint": { + "version": "3.1.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.46.0", - "@aws-sdk/credential-provider-imds": "3.46.0", - "@aws-sdk/credential-provider-sso": "3.46.0", - "@aws-sdk/credential-provider-web-identity": "3.46.0", - "@aws-sdk/property-provider": "3.46.0", - "@aws-sdk/shared-ini-file-loader": "3.46.0", - "@aws-sdk/types": "3.46.0", - "@aws-sdk/util-credentials": "3.46.0", - "tslib": "^2.3.0" + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/ie11-detection": { - "version": "2.0.2", + "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-serde": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "tslib": "^1.11.1" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/sha256-browser": { - "version": "2.0.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-stack": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/ie11-detection": "^2.0.0", - "@aws-crypto/sha256-js": "^2.0.0", - "@aws-crypto/supports-web-crypto": "^2.0.0", - "@aws-crypto/util": "^2.0.0", - "@aws-sdk/types": "^3.1.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/sha256-js": { - "version": "2.0.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/node-config-provider": { + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^2.0.0", - "@aws-sdk/types": "^3.1.0", - "tslib": "^1.11.1" + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/supports-web-crypto": { - "version": "2.0.2", + "node_modules/@aws-sdk/core/node_modules/@smithy/node-http-handler": { + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { - "tslib": "^1.11.1" + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/util": { - "version": "2.0.2", + "node_modules/@aws-sdk/core/node_modules/@smithy/property-provider": { + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.110.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/util/node_modules/@aws-sdk/types": { - "version": "3.342.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/util/node_modules/@aws-sdk/types/node_modules/tslib": { - "version": "2.5.3", - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/abort-controller": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/protocol-http": { + "version": "4.1.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/client-sso": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/querystring-builder": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "2.0.0", - "@aws-crypto/sha256-js": "2.0.0", - "@aws-sdk/config-resolver": "3.46.0", - "@aws-sdk/fetch-http-handler": "3.46.0", - "@aws-sdk/hash-node": "3.46.0", - "@aws-sdk/invalid-dependency": "3.46.0", - "@aws-sdk/middleware-content-length": "3.46.0", - "@aws-sdk/middleware-host-header": "3.46.0", - "@aws-sdk/middleware-logger": "3.46.0", - "@aws-sdk/middleware-retry": "3.46.0", - "@aws-sdk/middleware-serde": "3.46.0", - "@aws-sdk/middleware-stack": "3.46.0", - "@aws-sdk/middleware-user-agent": "3.46.0", - "@aws-sdk/node-config-provider": "3.46.0", - "@aws-sdk/node-http-handler": "3.46.0", - "@aws-sdk/protocol-http": "3.46.0", - "@aws-sdk/smithy-client": "3.46.0", - "@aws-sdk/types": "3.46.0", - "@aws-sdk/url-parser": "3.46.0", - "@aws-sdk/util-base64-browser": "3.46.0", - "@aws-sdk/util-base64-node": "3.46.0", - "@aws-sdk/util-body-length-browser": "3.46.0", - "@aws-sdk/util-body-length-node": "3.46.0", - "@aws-sdk/util-user-agent-browser": "3.46.0", - "@aws-sdk/util-user-agent-node": "3.46.0", - "@aws-sdk/util-utf8-browser": "3.46.0", - "@aws-sdk/util-utf8-node": "3.46.0", - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/config-resolver": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/querystring-parser": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/signature-v4": "3.46.0", - "@aws-sdk/types": "3.46.0", - "@aws-sdk/util-config-provider": "3.46.0", - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.46.0", - "@aws-sdk/property-provider": "3.46.0", - "@aws-sdk/shared-ini-file-loader": "3.46.0", - "@aws-sdk/types": "3.46.0", - "@aws-sdk/util-credentials": "3.46.0", - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/smithy-client": { + "version": "3.2.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/protocol-http": "3.46.0", - "@aws-sdk/querystring-builder": "3.46.0", - "@aws-sdk/types": "3.46.0", - "@aws-sdk/util-base64-browser": "3.46.0", - "tslib": "^2.3.0" + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/hash-node": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/types": { + "version": "3.3.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.46.0", - "@aws-sdk/util-buffer-from": "3.46.0", - "tslib": "^2.3.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/invalid-dependency": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/url-parser": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/is-array-buffer": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-base64": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-content-length": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/protocol-http": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/protocol-http": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-logger": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-middleware": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-retry": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-stream": { + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/protocol-http": "3.46.0", - "@aws-sdk/service-error-classification": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0", - "uuid": "^8.3.2" + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-serde": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-stack": { - "version": "3.46.0", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/credential-provider-env": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/protocol-http": "3.46.0", + "@aws-sdk/property-provider": "3.46.0", "@aws-sdk/types": "3.46.0", "tslib": "^2.3.0" }, @@ -4018,233 +4264,177 @@ "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/node-config-provider": { + "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/types": { "version": "3.46.0", "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/property-provider": "3.46.0", - "@aws-sdk/shared-ini-file-loader": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" - }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/node-http-handler": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.635.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/abort-controller": "3.46.0", - "@aws-sdk/protocol-http": "3.46.0", - "@aws-sdk/querystring-builder": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/protocol-http": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/querystring-builder": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/abort-controller": { + "version": "3.1.1", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.46.0", - "@aws-sdk/util-uri-escape": "3.46.0", - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/querystring-parser": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/service-error-classification": { - "version": "3.46.0", - "license": "Apache-2.0", - "engines": { - "node": ">= 12.0.0" + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/smithy-client": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-endpoint": { + "version": "3.1.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-stack": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/types": { - "version": "3.46.0", - "license": "Apache-2.0", - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/url-parser": { - "version": "3.46.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/querystring-parser": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-body-length-browser": { - "version": "3.46.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.3.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-body-length-node": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-serde": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-buffer-from": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-stack": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/is-array-buffer": "3.46.0", - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-config-provider": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-config-provider": { + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-credentials": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/shared-ini-file-loader": "3.46.0", - "tslib": "^2.3.0" + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-uri-escape": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/property-provider": { + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.46.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.46.0", - "bowser": "^2.11.0", - "tslib": "^2.3.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/protocol-http": { + "version": "4.1.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/node-config-provider": "3.46.0", - "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.46.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.3.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/uuid": { - "version": "8.3.2", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.637.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/querystring-builder": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.620.1", - "@aws-sdk/credential-provider-http": "3.635.0", - "@aws-sdk/credential-provider-ini": "3.637.0", - "@aws-sdk/credential-provider-process": "3.620.1", - "@aws-sdk/credential-provider-sso": "3.637.0", - "@aws-sdk/credential-provider-web-identity": "3.621.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.620.1", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/querystring-parser": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -4252,109 +4442,85 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.637.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.620.1", - "@aws-sdk/credential-provider-http": "3.635.0", - "@aws-sdk/credential-provider-process": "3.620.1", - "@aws-sdk/credential-provider-sso": "3.637.0", - "@aws-sdk/credential-provider-web-identity": "3.621.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.637.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.620.1", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/smithy-client": { + "version": "3.2.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.621.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/types": { + "version": "3.3.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.621.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/url-parser": { + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { + "@smithy/querystring-parser": "^3.0.3", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/credential-provider-imds": { - "version": "3.2.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-base64": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/node-config-provider": { - "version": "3.1.4", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/property-provider": { - "version": "3.1.3", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/querystring-parser": { + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-middleware": { "version": "3.0.3", "license": "Apache-2.0", "dependencies": { @@ -4365,19 +4531,25 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.4", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-stream": { + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4386,548 +4558,534 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/url-parser": { - "version": "3.0.3", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.37.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/property-provider": "3.37.0", - "@aws-sdk/shared-ini-file-loader": "3.37.0", - "@aws-sdk/types": "3.37.0", - "@aws-sdk/util-credentials": "3.37.0", - "tslib": "^2.3.0" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/property-provider": { - "version": "3.37.0", + "node_modules/@aws-sdk/credential-provider-imds": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.37.0", + "@aws-sdk/node-config-provider": "3.46.0", + "@aws-sdk/property-provider": "3.46.0", + "@aws-sdk/types": "3.46.0", + "@aws-sdk/url-parser": "3.46.0", "tslib": "^2.3.0" }, "engines": { - "node": ">= 10.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.37.0", + "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/node-config-provider": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/property-provider": "3.46.0", + "@aws-sdk/shared-ini-file-loader": "3.46.0", + "@aws-sdk/types": "3.46.0", "tslib": "^2.3.0" }, "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.637.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.637.0", - "@aws-sdk/token-providers": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/querystring-parser": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/property-provider": { - "version": "3.1.3", + "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/shared-ini-file-loader": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.4", + "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/types": { + "version": "3.46.0", "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/credential-provider-imds/node_modules/@aws-sdk/url-parser": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@aws-sdk/querystring-parser": "3.46.0", + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/credential-provider-ini": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/credential-provider-env": "3.46.0", + "@aws-sdk/credential-provider-imds": "3.46.0", + "@aws-sdk/credential-provider-sso": "3.46.0", + "@aws-sdk/credential-provider-web-identity": "3.46.0", "@aws-sdk/property-provider": "3.46.0", + "@aws-sdk/shared-ini-file-loader": "3.46.0", "@aws-sdk/types": "3.46.0", + "@aws-sdk/util-credentials": "3.46.0", "tslib": "^2.3.0" }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/types": { - "version": "3.46.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/ie11-detection": { + "version": "2.0.2", "license": "Apache-2.0", - "engines": { - "node": ">= 12.0.0" + "dependencies": { + "tslib": "^1.11.1" } }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.620.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { + "version": "1.14.1", + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/sha256-browser": { + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@aws-crypto/ie11-detection": "^2.0.0", + "@aws-crypto/sha256-js": "^2.0.0", + "@aws-crypto/supports-web-crypto": "^2.0.0", + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" } }, - "node_modules/@aws-sdk/middleware-host-header/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "1.14.1", + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/sha256-js": { + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "tslib": "^1.11.1" } }, - "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/protocol-http": { - "version": "4.1.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/supports-web-crypto": { + "version": "2.0.2", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "tslib": "^1.11.1" } }, - "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "1.14.1", + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/util": { + "version": "2.0.2", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@aws-sdk/types": "^3.110.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" } }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/util/node_modules/@aws-sdk/types": { + "version": "3.342.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "tslib": "^2.5.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/middleware-logger/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/util/node_modules/@aws-sdk/types/node_modules/tslib": { + "version": "2.5.3", + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "license": "0BSD" + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/abort-controller": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/middleware-logger/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/client-sso": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.46.0", + "@aws-sdk/fetch-http-handler": "3.46.0", + "@aws-sdk/hash-node": "3.46.0", + "@aws-sdk/invalid-dependency": "3.46.0", + "@aws-sdk/middleware-content-length": "3.46.0", + "@aws-sdk/middleware-host-header": "3.46.0", + "@aws-sdk/middleware-logger": "3.46.0", + "@aws-sdk/middleware-retry": "3.46.0", + "@aws-sdk/middleware-serde": "3.46.0", + "@aws-sdk/middleware-stack": "3.46.0", + "@aws-sdk/middleware-user-agent": "3.46.0", + "@aws-sdk/node-config-provider": "3.46.0", + "@aws-sdk/node-http-handler": "3.46.0", + "@aws-sdk/protocol-http": "3.46.0", + "@aws-sdk/smithy-client": "3.46.0", + "@aws-sdk/types": "3.46.0", + "@aws-sdk/url-parser": "3.46.0", + "@aws-sdk/util-base64-browser": "3.46.0", + "@aws-sdk/util-base64-node": "3.46.0", + "@aws-sdk/util-body-length-browser": "3.46.0", + "@aws-sdk/util-body-length-node": "3.46.0", + "@aws-sdk/util-user-agent-browser": "3.46.0", + "@aws-sdk/util-user-agent-node": "3.46.0", + "@aws-sdk/util-utf8-browser": "3.46.0", + "@aws-sdk/util-utf8-node": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=12.0.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.620.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/config-resolver": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/signature-v4": "3.46.0", + "@aws-sdk/types": "3.46.0", + "@aws-sdk/util-config-provider": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/client-sso": "3.46.0", + "@aws-sdk/property-provider": "3.46.0", + "@aws-sdk/shared-ini-file-loader": "3.46.0", + "@aws-sdk/types": "3.46.0", + "@aws-sdk/util-credentials": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/protocol-http": { - "version": "4.1.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/fetch-http-handler": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@aws-sdk/protocol-http": "3.46.0", + "@aws-sdk/querystring-builder": "3.46.0", + "@aws-sdk/types": "3.46.0", + "@aws-sdk/util-base64-browser": "3.46.0", + "tslib": "^2.3.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/hash-node": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@aws-sdk/types": "3.46.0", + "@aws-sdk/util-buffer-from": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/middleware-stack": { - "version": "3.342.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/invalid-dependency": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" } }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.637.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/is-array-buffer": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-content-length": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/protocol-http": "3.46.0", + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/protocol-http": { - "version": "4.1.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/protocol-http": "3.46.0", + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-logger": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/property-provider": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-retry": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/protocol-http": "3.46.0", + "@aws-sdk/service-error-classification": "3.46.0", "@aws-sdk/types": "3.46.0", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "uuid": "^8.3.2" }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/property-provider/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-serde": { "version": "3.46.0", "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" + }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.614.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-stack": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "tslib": "^2.6.2" + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/protocol-http": "3.46.0", + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/node-config-provider": { - "version": "3.1.4", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/node-config-provider": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/property-provider": "3.46.0", + "@aws-sdk/shared-ini-file-loader": "3.46.0", + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/property-provider": { - "version": "3.1.3", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/node-http-handler": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/abort-controller": "3.46.0", + "@aws-sdk/protocol-http": "3.46.0", + "@aws-sdk/querystring-builder": "3.46.0", + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.4", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/protocol-http": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/querystring-builder": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@aws-sdk/types": "3.46.0", + "@aws-sdk/util-uri-escape": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/util-config-provider": { - "version": "3.0.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/querystring-parser": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/util-middleware": { - "version": "3.0.3", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/service-error-classification": { + "version": "3.46.0", "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/signature-v4": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/shared-ini-file-loader": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/is-array-buffer": "3.46.0", - "@aws-sdk/types": "3.46.0", - "@aws-sdk/util-hex-encoding": "3.46.0", - "@aws-sdk/util-uri-escape": "3.46.0", "tslib": "^2.3.0" }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/signature-v4/node_modules/@aws-sdk/is-array-buffer": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/smithy-client": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/middleware-stack": "3.46.0", + "@aws-sdk/types": "3.46.0", "tslib": "^2.3.0" }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/signature-v4/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/types": { "version": "3.46.0", "license": "Apache-2.0", "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/signature-v4/node_modules/@aws-sdk/util-uri-escape": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/url-parser": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/querystring-parser": "3.46.0", + "@aws-sdk/types": "3.46.0", "tslib": "^2.3.0" - }, - "engines": { - "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/smithy-client": { - "version": "3.342.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-body-length-browser": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-stack": "3.342.0", - "@aws-sdk/types": "3.342.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" + "tslib": "^2.3.0" } }, - "node_modules/@aws-sdk/smithy-client/node_modules/@aws-sdk/types": { - "version": "3.342.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-body-length-node": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.3.0" }, "engines": { - "node": ">=14.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.614.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-buffer-from": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/is-array-buffer": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.614.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-config-provider": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/token-providers/node_modules/@smithy/property-provider": { - "version": "3.1.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/token-providers/node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.4", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/token-providers/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-credentials": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@aws-sdk/shared-ini-file-loader": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.37.0", - "license": "Apache-2.0", - "engines": { - "node": ">= 10.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/util-arn-parser": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-uri-escape": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { @@ -4937,83 +5095,105 @@ "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/util-base64-browser": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.46.0", + "bowser": "^2.11.0", "tslib": "^2.3.0" } }, - "node_modules/@aws-sdk/util-base64-node": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/util-buffer-from": "3.46.0", + "@aws-sdk/node-config-provider": "3.46.0", + "@aws-sdk/types": "3.46.0", "tslib": "^2.3.0" }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/util-base64-node/node_modules/@aws-sdk/is-array-buffer": { + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-utf8-browser": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { "tslib": "^2.3.0" - }, - "engines": { - "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/util-base64-node/node_modules/@aws-sdk/util-buffer-from": { - "version": "3.46.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/is-array-buffer": "3.46.0", - "tslib": "^2.3.0" - }, - "engines": { - "node": ">= 12.0.0" + "node_modules/@aws-sdk/credential-provider-ini/node_modules/uuid": { + "version": "8.3.2", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/@aws-sdk/util-credentials": { - "version": "3.37.0", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/shared-ini-file-loader": "3.37.0", - "tslib": "^2.3.0" + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-credentials/node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.37.0", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-endpoints": { + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.637.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", - "@smithy/util-endpoints": "^2.0.5", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" } }, - "node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -5021,48 +5201,46 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/util-hex-encoding": { - "version": "3.46.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.3.0" }, - "engines": { - "node": ">= 12.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" } }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.37.0", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/property-provider": { + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", - "bowser": "^2.11.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.3.0", @@ -5072,7 +5250,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@smithy/types": { + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/types": { "version": "3.3.0", "license": "Apache-2.0", "dependencies": { @@ -5082,31 +5260,50 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.614.0", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.37.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/property-provider": "3.37.0", + "@aws-sdk/shared-ini-file-loader": "3.37.0", + "@aws-sdk/types": "3.37.0", + "@aws-sdk/util-credentials": "3.37.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/property-provider": { + "version": "3.37.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.37.0", + "tslib": "^2.3.0" }, - "peerDependencies": { - "aws-crt": ">=1.0.0" + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/shared-ini-file-loader": { + "version": "3.37.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.0" }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/@aws-sdk/util-user-agent-node/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -5114,12 +5311,10 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/node-config-provider": { - "version": "3.1.4", + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -5127,7 +5322,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/property-provider": { + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/property-provider": { "version": "3.1.3", "license": "Apache-2.0", "dependencies": { @@ -5138,7 +5333,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/shared-ini-file-loader": { + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.4", "license": "Apache-2.0", "dependencies": { @@ -5149,7 +5344,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/types": { + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/types": { "version": "3.3.0", "license": "Apache-2.0", "dependencies": { @@ -5159,772 +5354,627 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/util-utf8-node": { + "node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/util-buffer-from": "3.46.0", + "@aws-sdk/property-provider": "3.46.0", + "@aws-sdk/types": "3.46.0", "tslib": "^2.3.0" }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/util-utf8-node/node_modules/@aws-sdk/is-array-buffer": { + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/types": { "version": "3.46.0", "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.3.0" - }, "engines": { "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/util-utf8-node/node_modules/@aws-sdk/util-buffer-from": { - "version": "3.46.0", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/is-array-buffer": "3.46.0", - "tslib": "^2.3.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-toolkits/telemetry": { - "version": "1.0.258", - "dev": true, + "node_modules/@aws-sdk/middleware-host-header/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "ajv": "^6.12.6", - "fs-extra": "^11.1.0", - "lodash": "^4.17.20", - "prettier": "^3.3.2", - "ts-morph": "^23.0.0", - "yargs": "^17.0.1" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-toolkits/telemetry/node_modules/fs-extra": { - "version": "11.2.0", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/protocol-http": { + "version": "4.1.0", + "license": "Apache-2.0", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.14" + "node": ">=16.0.0" } }, - "node_modules/@aws/fully-qualified-names": { - "version": "2.1.4", - "dev": true, + "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/types": { + "version": "3.3.0", "license": "Apache-2.0", "dependencies": { - "web-tree-sitter": "^0.20.8" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws/mynah-ui": { - "version": "4.15.11", - "hasInstallScript": true, - "license": "Apache License 2.0", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", + "license": "Apache-2.0", "dependencies": { - "escape-html": "^1.0.3", - "just-clone": "^6.2.0", - "marked": "^12.0.2", - "prismjs": "1.29.0", - "sanitize-html": "^2.12.1", - "unescape-html": "^1.1.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "escape-html": "^1.0.3", - "just-clone": "^6.2.0", - "marked": "^12.0.2", - "prismjs": "1.29.0", - "sanitize-html": "^2.12.1", - "unescape-html": "^1.1.0" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws/mynah-ui/node_modules/marked": { - "version": "12.0.2", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" + "node_modules/@aws-sdk/middleware-logger/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 18" + "node": ">=16.0.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.23.5", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-logger/node_modules/@smithy/types": { + "version": "3.3.0", + "license": "Apache-2.0", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "license": "Apache-2.0", "dependencies": { - "color-convert": "^1.9.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/protocol-http": { + "version": "4.1.0", + "license": "Apache-2.0", "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.8.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/types": { + "version": "3.3.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-stack": { + "version": "3.342.0", + "license": "Apache-2.0", "dependencies": { - "has-flag": "^3.0.0" + "tslib": "^2.5.0" }, "engines": { - "node": ">=4" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.637.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/protocol-http": { + "version": "4.1.0", + "license": "Apache-2.0", "dependencies": { - "color-convert": "^1.9.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/types": { + "version": "3.3.0", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/property-provider": { + "version": "3.46.0", + "license": "Apache-2.0", "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", + "@aws-sdk/types": "3.46.0", + "tslib": "^2.3.0" + }, "engines": { - "node": ">=0.8.0" + "node": ">= 12.0.0" } }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/property-provider/node_modules/@aws-sdk/types": { + "version": "3.46.0", + "license": "Apache-2.0", "engines": { - "node": ">=4" + "node": ">= 12.0.0" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "license": "Apache-2.0", "dependencies": { - "has-flag": "^3.0.0" + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.23.6", - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" + "node_modules/@aws-sdk/region-config-resolver/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=16.0.0" } }, - "node_modules/@bcherny/json-schema-ref-parser": { - "version": "10.0.5-fork", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/node-config-provider": { + "version": "3.1.4", + "license": "Apache-2.0", "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.6", - "call-me-maybe": "^1.0.1", - "js-yaml": "^4.1.0" + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" + "node": ">=16.0.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "license": "MIT", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/property-provider": { + "version": "3.1.3", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.1.90" + "node": ">=16.0.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.4", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=16.0.0" } }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/types": { + "version": "3.3.0", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.2", - "license": "MIT", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/util-middleware": { + "version": "3.0.3", + "license": "Apache-2.0", "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.5", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/signature-v4": { + "version": "3.46.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/is-array-buffer": "3.46.0", + "@aws-sdk/types": "3.46.0", + "@aws-sdk/util-hex-encoding": "3.46.0", + "@aws-sdk/util-uri-escape": "3.46.0", + "tslib": "^2.3.0" + }, "engines": { - "node": ">=10.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/signature-v4/node_modules/@aws-sdk/is-array-buffer": { + "version": "3.46.0", + "license": "Apache-2.0", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "tslib": "^2.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/signature-v4/node_modules/@aws-sdk/types": { + "version": "3.46.0", + "license": "Apache-2.0", "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/signature-v4/node_modules/@aws-sdk/util-uri-escape": { + "version": "3.46.0", + "license": "Apache-2.0", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "tslib": "^2.3.0" }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.56.0", - "dev": true, - "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@gerhobbelt/gitignore-parser": { - "version": "0.2.0-9", - "license": "Apache License, Version 2.0", + "node_modules/@aws-sdk/smithy-client": { + "version": "3.342.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-stack": "3.342.0", + "@aws-sdk/types": "3.342.0", + "tslib": "^2.5.0" + }, "engines": { - "node": ">=10" + "node": ">=14.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "dev": true, + "node_modules/@aws-sdk/smithy-client/node_modules/@aws-sdk/types": { + "version": "3.342.0", "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "tslib": "^2.5.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=14.0.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, + "node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12.22" + "node": ">=16.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@iarna/toml": { - "version": "2.2.5", - "license": "ISC" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "license": "ISC", + "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=16.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "license": "MIT", - "engines": { - "node": ">=12" + "node_modules/@aws-sdk/token-providers/node_modules/@smithy/property-provider": { + "version": "3.1.3", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "license": "MIT", - "engines": { - "node": ">=12" + "node_modules/@aws-sdk/token-providers/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "license": "MIT", + "node_modules/@aws-sdk/token-providers/node_modules/@smithy/types": { + "version": "3.3.0", + "license": "Apache-2.0", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "node_modules/@aws-sdk/types": { + "version": "3.37.0", + "license": "Apache-2.0", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">= 10.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "license": "MIT", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.46.0", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "tslib": "^2.3.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">= 12.0.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node_modules/@aws-sdk/util-base64-browser": { + "version": "3.46.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-base64-node": { + "version": "3.46.0", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@aws-sdk/util-buffer-from": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-base64-node/node_modules/@aws-sdk/is-array-buffer": { + "version": "3.46.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-base64-node/node_modules/@aws-sdk/util-buffer-from": { + "version": "3.46.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/is-array-buffer": "3.46.0", + "tslib": "^2.3.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-credentials": { + "version": "3.37.0", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@aws-sdk/shared-ini-file-loader": "3.37.0", + "tslib": "^2.3.0" + }, + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-credentials/node_modules/@aws-sdk/shared-ini-file-loader": { + "version": "3.37.0", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "tslib": "^2.3.0" + }, + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@koa/cors": { - "version": "5.0.0", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.637.0", + "license": "Apache-2.0", "dependencies": { - "vary": "^1.1.2" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@koa/router": { - "version": "12.0.1", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", "dependencies": { - "debug": "^4.3.4", - "http-errors": "^2.0.0", - "koa-compose": "^4.1.0", - "methods": "^1.1.2", - "path-to-regexp": "^6.2.1" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12" + "node": ">=16.0.0" } }, - "node_modules/@koa/router/node_modules/path-to-regexp": { - "version": "6.3.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/types": { + "version": "3.3.0", + "license": "Apache-2.0", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">= 8" + "node": ">=16.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-hex-encoding": { + "version": "3.46.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">= 8" + "node": ">= 12.0.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.37.0", + "license": "Apache-2.0", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "tslib": "^2.3.0" }, "engines": { - "node": ">= 8" + "node": ">= 10.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@aws-sdk/types": { + "version": "3.609.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://opencollective.com/unts" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@playwright/browser-chromium": { - "version": "1.43.1", - "dev": true, - "hasInstallScript": true, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@smithy/types": { + "version": "3.3.0", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.43.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=16.0.0" } }, - "node_modules/@playwright/browser-chromium/node_modules/playwright-core": { - "version": "1.43.1", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@sindresorhus/is": { - "version": "4.2.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^2.0.0" - } - }, - "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "6.1.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.1", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, - "node_modules/@smithy/abort-controller": { - "version": "2.2.0", + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@smithy/core": { - "version": "2.4.0", + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", "@smithy/types": "^3.3.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/abort-controller": { - "version": "3.1.1", + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/node-config-provider": { + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -5932,968 +5982,3038 @@ "node": ">=16.0.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/property-provider": { + "version": "3.1.3", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@smithy/core/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/middleware-endpoint": { - "version": "3.1.0", + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.4", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/shared-ini-file-loader": "^3.1.4", "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-middleware": "^3.0.3", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/middleware-retry": { - "version": "3.0.15", + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/types": { + "version": "3.3.0", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/service-error-classification": "^3.0.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/middleware-serde": { - "version": "3.0.3", + "node_modules/@aws-sdk/util-utf8-node": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/util-buffer-from": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/middleware-stack": { - "version": "3.0.3", + "node_modules/@aws-sdk/util-utf8-node/node_modules/@aws-sdk/is-array-buffer": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/node-config-provider": { - "version": "3.1.4", + "node_modules/@aws-sdk/util-utf8-node/node_modules/@aws-sdk/util-buffer-from": { + "version": "3.46.0", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@aws-sdk/is-array-buffer": "3.46.0", + "tslib": "^2.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/node-http-handler": { - "version": "3.1.4", - "license": "Apache-2.0", + "node_modules/@aws-toolkits/telemetry": { + "version": "1.0.289", + "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.289.tgz", + "integrity": "sha512-srzr3JGMprOX2rrUAhribVBrUMfvR6uOhwksaxu63/GMTBjEWjwfcKzpgQzxu1+InmGioBa4zKdKKV/hAaUCmw==", + "dev": true, "dependencies": { - "@smithy/abort-controller": "^3.1.1", - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "ajv": "^6.12.6", + "cross-spawn": "^7.0.6", + "fs-extra": "^11.1.0", + "lodash": "^4.17.20", + "prettier": "^3.3.2", + "ts-morph": "^23.0.0", + "yargs": "^17.0.1" } }, - "node_modules/@smithy/core/node_modules/@smithy/property-provider": { - "version": "3.1.3", - "license": "Apache-2.0", + "node_modules/@aws-toolkits/telemetry/node_modules/fs-extra": { + "version": "11.2.0", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=14.14" } }, - "node_modules/@smithy/core/node_modules/@smithy/protocol-http": { - "version": "4.1.0", + "node_modules/@aws/fully-qualified-names": { + "version": "2.1.4", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "web-tree-sitter": "^0.20.8" } }, - "node_modules/@smithy/core/node_modules/@smithy/querystring-builder": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@aws/mynah-ui": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@aws/mynah-ui/-/mynah-ui-4.21.3.tgz", + "integrity": "sha512-iHFGmLg8fZgoqiHHDP94m6+ZmBsIiP7NBte/WId3iv+1344+Sipm/nNoZlczx5mIV1qhD+r/IZKG4c9Er4sHuA==", + "hasInstallScript": true, "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-uri-escape": "^3.0.0", - "tslib": "^2.6.2" + "escape-html": "^1.0.3", + "just-clone": "^6.2.0", + "marked": "^14.1.0", + "prismjs": "1.29.0", + "sanitize-html": "^2.12.1", + "unescape-html": "^1.1.0" }, - "engines": { - "node": ">=16.0.0" + "peerDependencies": { + "escape-html": "^1.0.3", + "just-clone": "^6.2.0", + "marked": "^12.0.2", + "prismjs": "1.29.0", + "sanitize-html": "^2.12.1", + "unescape-html": "^1.1.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/querystring-parser": { - "version": "3.0.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "node_modules/@aws/mynah-ui/node_modules/marked": { + "version": "14.1.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.4.tgz", + "integrity": "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" }, "engines": { - "node": ">=16.0.0" + "node": ">= 18" } }, - "node_modules/@smithy/core/node_modules/@smithy/service-error-classification": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.3.0" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.4", - "license": "Apache-2.0", + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=4" } }, - "node_modules/@smithy/core/node_modules/@smithy/smithy-client": { - "version": "3.2.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.1.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/core/node_modules/@smithy/types": { - "version": "3.3.0", - "license": "Apache-2.0", + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=4" } }, - "node_modules/@smithy/core/node_modules/@smithy/url-parser": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/querystring-parser": "^3.0.3", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "color-name": "1.1.3" } }, - "node_modules/@smithy/core/node_modules/@smithy/util-base64": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=0.8.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/util-body-length-browser": { + "node_modules/@babel/code-frame/node_modules/has-flag": { "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/@smithy/core/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=4" } }, - "node_modules/@smithy/core/node_modules/@smithy/util-hex-encoding": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/util-middleware": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@babel/highlight": { + "version": "7.23.4", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/util-retry": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/service-error-classification": "^3.0.3", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=4" } }, - "node_modules/@smithy/core/node_modules/@smithy/util-stream": { - "version": "3.1.3", - "license": "Apache-2.0", + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=4" } }, - "node_modules/@smithy/core/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "color-name": "1.1.3" } }, - "node_modules/@smithy/core/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=0.8.0" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "2.5.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^3.3.0", - "@smithy/querystring-builder": "^2.2.0", - "@smithy/types": "^2.12.0", - "@smithy/util-base64": "^2.3.0", - "tslib": "^2.6.2" + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "license": "Apache-2.0", + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=4" } }, - "node_modules/@smithy/middleware-endpoint": { - "version": "2.5.1", - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^2.3.0", - "@smithy/node-config-provider": "^2.3.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "@smithy/url-parser": "^2.2.0", - "@smithy/util-middleware": "^2.2.0", - "tslib": "^2.6.2" + "node_modules/@babel/parser": { + "version": "7.23.6", + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.0.0" } }, - "node_modules/@smithy/middleware-retry": { - "version": "2.3.1", - "license": "Apache-2.0", + "node_modules/@babel/runtime": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz", + "integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^2.3.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/service-error-classification": "^2.1.5", - "@smithy/smithy-client": "^2.5.1", - "@smithy/types": "^2.12.0", - "@smithy/util-middleware": "^2.2.0", - "@smithy/util-retry": "^2.2.0", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "regenerator-runtime": "^0.14.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@smithy/middleware-serde": { - "version": "2.3.0", - "license": "Apache-2.0", + "node_modules/@bcherny/json-schema-ref-parser": { + "version": "10.0.5-fork", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" }, "engines": { - "node": ">=14.0.0" + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@smithy/middleware-stack": { - "version": "2.2.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=0.1.90" } }, - "node_modules/@smithy/node-config-provider": { - "version": "2.3.0", - "license": "Apache-2.0", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/property-provider": "^2.2.0", - "@smithy/shared-ini-file-loader": "^2.4.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=14.0.0" + "node": ">=12" } }, - "node_modules/@smithy/node-http-handler": { - "version": "2.5.0", - "license": "Apache-2.0", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/abort-controller": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/querystring-builder": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@smithy/property-provider": { - "version": "2.2.0", - "license": "Apache-2.0", + "node_modules/@dabh/diagnostics": { + "version": "2.0.2", + "license": "MIT", "dependencies": { - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" } }, - "node_modules/@smithy/protocol-http": { - "version": "3.3.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.5", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=10.0.0" } }, - "node_modules/@smithy/querystring-builder": { - "version": "2.2.0", - "license": "Apache-2.0", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^2.12.0", - "@smithy/util-uri-escape": "^2.2.0", - "tslib": "^2.6.2" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@smithy/querystring-parser": { - "version": "2.2.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@smithy/service-error-classification": { - "version": "2.1.5", - "license": "Apache-2.0", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^2.12.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "2.4.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@smithy/signature-v4": { - "version": "4.1.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@gerhobbelt/gitignore-parser": { + "version": "0.2.0-9", + "license": "Apache License, Version 2.0", "engines": { - "node": ">=16.0.0" + "node": ">=10" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "dev": true, "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=16.0.0" + "node": ">=10.10.0" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/protocol-http": { - "version": "4.1.0", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=16.0.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/types": { - "version": "3.3.0", - "license": "Apache-2.0", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "license": "ISC" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "license": "ISC", "dependencies": { - "tslib": "^2.6.2" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=12" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/util-hex-encoding": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/util-middleware": { - "version": "3.0.3", - "license": "Apache-2.0", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "license": "MIT", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/smithy-client": { - "version": "2.5.1", - "license": "Apache-2.0", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/middleware-endpoint": "^2.5.1", - "@smithy/middleware-stack": "^2.2.0", - "@smithy/protocol-http": "^3.3.0", - "@smithy/types": "^2.12.0", - "@smithy/util-stream": "^2.2.0", - "tslib": "^2.6.2" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.0.0" } }, - "node_modules/@smithy/types": { - "version": "2.12.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=6.0.0" } }, - "node_modules/@smithy/url-parser": { - "version": "2.2.0", - "license": "Apache-2.0", + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/querystring-parser": "^2.2.0", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@smithy/util-base64": { - "version": "2.3.0", - "license": "Apache-2.0", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "license": "Apache-2.0", + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@koa/cors": { + "version": "5.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" + "vary": "^1.1.2" }, "engines": { - "node": ">=14.0.0" + "node": ">= 14.0.0" } }, - "node_modules/@smithy/util-endpoints": { - "version": "2.0.5", - "license": "Apache-2.0", + "node_modules/@koa/router": { + "version": "12.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "debug": "^4.3.4", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "methods": "^1.1.2", + "path-to-regexp": "^6.2.1" }, "engines": { - "node": ">=16.0.0" + "node": ">= 12" } }, - "node_modules/@smithy/util-endpoints/node_modules/@smithy/node-config-provider": { - "version": "3.1.4", - "license": "Apache-2.0", + "node_modules/@koa/router/node_modules/path-to-regexp": { + "version": "6.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">=16.0.0" + "node": ">= 8" } }, - "node_modules/@smithy/util-endpoints/node_modules/@smithy/property-provider": { - "version": "3.1.3", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">= 8" } }, - "node_modules/@smithy/util-endpoints/node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.4", - "license": "Apache-2.0", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 8" } }, - "node_modules/@smithy/util-endpoints/node_modules/@smithy/types": { - "version": "3.3.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "license": "MIT", + "optional": true, "engines": { - "node": ">=16.0.0" + "node": ">=14" } }, - "node_modules/@smithy/util-hex-encoding": { - "version": "2.2.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, - "node_modules/@smithy/util-middleware": { - "version": "2.2.0", + "node_modules/@playwright/browser-chromium": { + "version": "1.43.1", + "dev": true, + "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" + "playwright-core": "1.43.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" } }, - "node_modules/@smithy/util-retry": { - "version": "2.2.0", + "node_modules/@playwright/browser-chromium/node_modules/playwright-core": { + "version": "1.43.1", + "dev": true, "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^2.1.5", - "@smithy/types": "^2.12.0", - "tslib": "^2.6.2" + "bin": { + "playwright-core": "cli.js" }, "engines": { - "node": ">= 14.0.0" + "node": ">=16" } }, - "node_modules/@smithy/util-stream": { - "version": "2.2.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^2.5.0", - "@smithy/node-http-handler": "^2.5.0", - "@smithy/types": "^2.12.0", - "@smithy/util-base64": "^2.3.0", - "@smithy/util-buffer-from": "^2.2.0", - "@smithy/util-hex-encoding": "^2.2.0", - "@smithy/util-utf8": "^2.3.0", - "tslib": "^2.6.2" - }, + "node_modules/@sindresorhus/is": { + "version": "4.2.0", + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@smithy/util-uri-escape": { - "version": "2.2.0", - "license": "Apache-2.0", + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" + "type-detect": "4.0.8" } }, - "node_modules/@smithy/util-utf8": { - "version": "2.3.0", + "node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "6.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" + }, + "node_modules/@smithy/abort-controller": { + "version": "2.2.0", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@smithy/types": "^2.12.0", "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@smithy/util-waiter": { - "version": "3.1.2", + "node_modules/@smithy/config-resolver": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.10.tgz", + "integrity": "sha512-Uh0Sz9gdUuz538nvkPiyv1DZRX9+D15EKDtnQP5rYVAzM/dnYk3P8cg73jcxyOitPgT3mE3OVj7ky7sibzHWkw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.1", - "@smithy/types": "^3.3.0", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.8", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/util-waiter/node_modules/@smithy/abort-controller": { - "version": "3.1.1", + "node_modules/@smithy/config-resolver/node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/util-waiter/node_modules/@smithy/types": { - "version": "3.3.0", + "node_modules/@smithy/config-resolver/node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", "license": "Apache-2.0", "dependencies": { + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "license": "MIT", + "node_modules/@smithy/config-resolver/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "license": "Apache-2.0", "dependencies": { - "defer-to-connect": "^2.0.0" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" + "node": ">=16.0.0" } }, - "node_modules/@ts-morph/common": { - "version": "0.24.0", - "dev": true, - "license": "MIT", + "node_modules/@smithy/config-resolver/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", "dependencies": { - "fast-glob": "^3.3.2", - "minimatch": "^9.0.4", - "mkdirp": "^3.0.1", - "path-browserify": "^1.0.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@ts-morph/common/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", + "node_modules/@smithy/config-resolver/node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "9.0.5", - "dev": true, - "license": "ISC", + "node_modules/@smithy/core": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.1.tgz", + "integrity": "sha512-DujtuDA7BGEKExJ05W5OdxCoyekcKT3Rhg1ZGeiUWaz2BJIWXjZmsG/DIP4W48GHno7AQwRsaCb8NcBgH3QZpg==", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^2.0.1" + "@smithy/middleware-serde": "^3.0.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-stream": "^3.2.1", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16.0.0" } }, - "node_modules/@ts-morph/common/node_modules/mkdirp": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "node_modules/@smithy/core/node_modules/@smithy/abort-controller": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", + "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16.0.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/adm-zip": { - "version": "0.4.34", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/fetch-http-handler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", + "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", + "license": "Apache-2.0", "dependencies": { - "@types/node": "*" + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@types/archiver": { - "version": "6.0.2", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", "dependencies": { - "@types/readdir-glob": "*" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@types/async-lock": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/middleware-serde": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", + "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", + "license": "Apache-2.0", "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@types/bonjour": { - "version": "3.5.10", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/node-http-handler": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", + "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", + "license": "Apache-2.0", "dependencies": { - "@types/node": "*" + "@smithy/abort-controller": "^3.1.6", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@types/bytes": { - "version": "3.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.2", - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "license": "Apache-2.0", "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@types/circular-dependency-plugin": { - "version": "5.0.8", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/querystring-builder": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", + "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", + "license": "Apache-2.0", "dependencies": { - "@types/node": "*", - "webpack": "^5.1.0" + "@smithy/types": "^3.6.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@types/connect": { - "version": "3.4.35", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", "dependencies": { - "@types/node": "*" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.3.5", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "license": "Apache-2.0", "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@types/cross-spawn": { - "version": "6.0.6", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core/node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core/node_modules/@smithy/util-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", + "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.5.tgz", + "integrity": "sha512-4FTQGAsuwqTzVMmiRVTn0RR9GrbRfkP0wfu/tXWVHd2LgNpTY0uglQpIScXK4NaEyXbB3JmZt8gfVqO50lP8wg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.9", + "@smithy/property-provider": "^3.1.8", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/querystring-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", + "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/url-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", + "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.7.tgz", + "integrity": "sha512-kVSXScIiRN7q+s1x7BrQtZ1Aa9hvvP9FeCqCdBxv37GimIHgBCOnZ5Ip80HLt0DhnAKpiobFdGqTFgbaJNrazA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.6.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/eventstream-codec/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-codec/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.11.tgz", + "integrity": "sha512-Pd1Wnq3CQ/v2SxRifDUihvpXzirJYbbtXfEnnLV/z0OGCTx/btVX74P86IgrZkjOydOASBGXdPpupYQI+iO/6A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.10", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.8.tgz", + "integrity": "sha512-zkFIG2i1BLbfoGQnf1qEeMqX0h5qAznzaZmMVNnvPZz9J5AWBPkOMckZWPedGUPcVITacwIdQXoPcdIQq5FRcg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.10.tgz", + "integrity": "sha512-hjpU1tIsJ9qpcoZq9zGHBJPBOeBGYt+n8vfhDwnITPhEre6APrvqq/y3XMDEGUT2cWQ4ramNqBPRbx3qn55rhw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.10", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.10.tgz", + "integrity": "sha512-ewG1GHbbqsFZ4asaq40KmxCmXO+AFSM1b+DcO2C03dyJj/ZH71CiTg853FSE/3SHK9q3jiYQIFjlGSwfxQ9kww==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^3.1.7", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "2.5.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^3.3.0", + "@smithy/querystring-builder": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/util-base64": "^2.3.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-node": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.8.tgz", + "integrity": "sha512-tlNQYbfpWXHimHqrvgo14DrMAgUBua/cNoz9fMYcDmYej7MAmUcjav/QKQbFc3NrcPxeJ7QClER4tWZmfwoPng==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-node/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-node/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-node/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.8.tgz", + "integrity": "sha512-7Qynk6NWtTQhnGTTZwks++nJhQ1O54Mzi7fz4PqZOiYXb4Z1Flpb2yRvdALoggTS8xjtohWUM+RygOtB30YL3Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/invalid-dependency/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.10.tgz", + "integrity": "sha512-T4dIdCs1d/+/qMpwhJ1DzOhxCZjZHbHazEPJWdB4GDi2HjIZllVzeBEcdJUN0fomV8DURsgOyrbEUzg3vzTaOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-content-length/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-content-length/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "2.5.1", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^2.3.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "2.3.1", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/service-error-classification": "^2.1.5", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "2.3.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "2.3.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "2.5.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/querystring-builder": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "3.3.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.12.0", + "@smithy/util-uri-escape": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "2.1.5", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.12.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "2.4.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.1.tgz", + "integrity": "sha512-NsV1jF4EvmO5wqmaSzlnTVetemBS3FZHdyc5CExbDljcyJCEEkJr8ANu2JvtNbVg/9MvKAWV44kTrGS+Pi4INg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "2.5.1", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "2.12.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-base64": { + "version": "2.3.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.25.tgz", + "integrity": "sha512-fRw7zymjIDt6XxIsLwfJfYUfbGoO9CmCJk6rjJ/X5cd20+d2Is7xjU5Kt/AiDt6hX8DAf5dztmfP5O82gR9emA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.8", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/abort-controller": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", + "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/fetch-http-handler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", + "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/middleware-endpoint": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", + "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-middleware": "^3.0.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/middleware-serde": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", + "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/middleware-stack": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", + "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/node-http-handler": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", + "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.6", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/querystring-builder": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", + "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/querystring-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", + "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/smithy-client": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", + "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-stream": "^3.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/url-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", + "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/util-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", + "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.25.tgz", + "integrity": "sha512-H3BSZdBDiVZGzt8TG51Pd2FvFO0PAx/A0mJ0EH8a13KJ6iUCdYnw/Dk/MdC1kTd0eUuUGisDFaxXVXo4HHFL1g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^3.0.10", + "@smithy/credential-provider-imds": "^3.2.5", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/property-provider": "^3.1.8", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/abort-controller": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", + "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/fetch-http-handler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", + "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/middleware-endpoint": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", + "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-middleware": "^3.0.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/middleware-serde": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", + "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/middleware-stack": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", + "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/node-http-handler": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", + "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.6", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/querystring-builder": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", + "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/querystring-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", + "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/smithy-client": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", + "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-stream": "^3.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/url-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", + "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/util-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", + "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.4.tgz", + "integrity": "sha512-kPt8j4emm7rdMWQyL0F89o92q10gvCUa6sBkBtDJ7nV2+P7wpXczzOfoDJ49CKXe5CCqb8dc1W+ZdLlrKzSAnQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^2.1.5", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-hex-encoding": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "2.2.0", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.7.tgz", + "integrity": "sha512-d5yGlQtmN/z5eoTtIYgkvOw27US2Ous4VycnXatyoImIF9tzlcpnKqQ/V7qhvJmb2p6xZne1NopCLakdTnkBBQ==", + "dependencies": { + "@smithy/abort-controller": "^3.1.6", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-waiter/node_modules/@smithy/abort-controller": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", + "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-waiter/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.11.0.tgz", + "integrity": "sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.13.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", + "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "minimatch": "^9.0.4", + "mkdirp": "^3.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@ts-morph/common/node_modules/mkdirp": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/adm-zip": { + "version": "0.4.34", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/async-lock": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/bytes": { + "version": "3.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.2", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "node_modules/@types/circular-dependency-plugin": { + "version": "5.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "webpack": "^5.1.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cross-spawn": { + "version": "6.0.6", + "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -6940,14 +9060,6 @@ "@types/range-parser": "*" } }, - "node_modules/@types/fs-extra": { - "version": "9.0.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/glob": { "version": "8.1.0", "dev": true, @@ -6962,6 +9074,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/he": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", + "integrity": "sha512-q67/qwlxblDzEDvzHhVkwc1gzVWxaNxeyHUBF4xElrvjL11O+Ytze+1fGpBHlr/H9myiBUaUXNnNPmBHxxfAcA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", "license": "MIT" @@ -7056,8 +9175,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "14.18.63", - "license": "MIT" + "version": "22.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz", + "integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==", + "dependencies": { + "undici-types": "~6.19.8" + } }, "node_modules/@types/node-fetch": { "version": "2.6.9", @@ -7102,18 +9225,11 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/readdir-glob": { - "version": "1.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/readline-sync": { - "version": "1.4.4", - "dev": true, - "license": "MIT" + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@types/readline-sync/-/readline-sync-1.4.8.tgz", + "integrity": "sha512-BL7xOf0yKLA6baAX6MMOnYkoflUyj/c7y3pqMRfU0va7XlwHAOTOIo4x55P/qLfMsuaYdJJKubToLqRVmRtRZA==", + "dev": true }, "node_modules/@types/responselike": { "version": "1.0.0", @@ -7197,8 +9313,9 @@ "license": "MIT" }, "node_modules/@types/uuid": { - "version": "9.0.1", - "dev": true, + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", "license": "MIT" }, "node_modules/@types/vscode": { @@ -7217,9 +9334,10 @@ "license": "MIT" }, "node_modules/@types/webpack-env": { - "version": "1.18.1", - "dev": true, - "license": "MIT" + "version": "1.18.5", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.5.tgz", + "integrity": "sha512-wz7kjjRRj8/Lty4B+Kr0LN6Ypc/3SymeCCGSbaXp2leH0ZVg/PriNiOwNj4bD4uphI7A8NXS4b6Gl373sfO5mA==", + "dev": true }, "node_modules/@types/whatwg-url": { "version": "11.0.4", @@ -8018,21 +10136,21 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.41", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.41.tgz", + "integrity": "sha512-EMxPWXlEqqvsK9jxPmNvEShrIXP2LYTdQnEfsBH6OQCnlZRVo/dJIgtzbKvtK9A8PBTDQdxwxulj+QKplmW2Xg==", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } + }, "node_modules/a-sync-waterfall": { "version": "1.0.1", "dev": true, "license": "MIT" }, - "node_modules/abort-controller": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/accepts": { "version": "1.3.8", "dev": true, @@ -8046,7 +10164,9 @@ } }, "node_modules/acorn": { - "version": "8.12.0", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", "bin": { @@ -8162,8 +10282,9 @@ "link": true }, "node_modules/amazon-states-language-service": { - "version": "1.11.0", - "license": "MIT", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/amazon-states-language-service/-/amazon-states-language-service-1.13.0.tgz", + "integrity": "sha512-XT/7LL9+TRCB8H3t0kM6h2uivHa7Pn2lZGpvHKujH1MM+lQ7aaprAKrnZkfSk9++VFNbFJBAnKW+5NN2xVcvlA==", "dependencies": { "js-yaml": "^4.1.0", "vscode-json-languageservice": "5.3.5", @@ -8223,221 +10344,51 @@ ], "license": "Apache-2.0", "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "4.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/archiver": { - "version": "7.0.1", - "license": "MIT", - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/archiver-utils": { - "version": "5.0.2", - "license": "MIT", - "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/archiver-utils/node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/archiver-utils/node_modules/events": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/archiver-utils/node_modules/ieee754": { - "version": "1.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "4.5.2", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/archiver/node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/archiver/node_modules/buffer-crc32": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/archiver/node_modules/events": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/archiver/node_modules/ieee754": { - "version": "1.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "4.5.2", + "node_modules/ansi-styles": { + "version": "4.3.0", "license": "MIT", "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/archiver/node_modules/tar-stream": { - "version": "3.1.7", + "node_modules/ansi-wrap": { + "version": "0.1.0", "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, "node_modules/arg": { @@ -8696,6 +10647,7 @@ }, "node_modules/b4a": { "version": "1.6.4", + "dev": true, "license": "ISC" }, "node_modules/balanced-match": { @@ -8704,6 +10656,7 @@ }, "node_modules/bare-events": { "version": "2.2.2", + "dev": true, "license": "Apache-2.0", "optional": true }, @@ -9538,9 +11491,10 @@ } }, "node_modules/code-block-writer": { - "version": "13.0.1", - "dev": true, - "license": "MIT" + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.2.tgz", + "integrity": "sha512-XfXzAGiStXSmCIwrkdfvc7FS5Dtj8yelCtyOf2p2skCAfvLd6zu0rGzuS9NSCO3bq1JKpFZ7tbKdKlcd5occQA==", + "dev": true }, "node_modules/color": { "version": "3.2.1", @@ -9622,81 +11576,6 @@ "node": ">= 6" } }, - "node_modules/compress-commons": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/compress-commons/node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/compress-commons/node_modules/events": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/compress-commons/node_modules/ieee754": { - "version": "1.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/compress-commons/node_modules/readable-stream": { - "version": "4.5.2", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/compressible": { "version": "2.0.18", "dev": true, @@ -9755,6 +11634,64 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/concurrently": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.0.0.tgz", + "integrity": "sha512-WKM7PUsI8wyXpF80H+zjHP32fsgsHNQfPLw/e70Z5dYkV7hF+rf8q3D+ScWJIEr57CpkO3OWBko6hwhQLPR8Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "date-fns": "^2.16.1", + "lodash": "^4.17.21", + "rxjs": "^6.6.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^8.1.0", + "tree-kill": "^1.2.2", + "yargs": "^16.2.0" + }, + "bin": { + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.0 || >=16.0.0" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "dev": true, @@ -9788,9 +11725,10 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9902,88 +11840,6 @@ "node": ">=4" } }, - "node_modules/crc-32": { - "version": "1.2.2", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc32-stream": { - "version": "6.0.0", - "license": "MIT", - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/crc32-stream/node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/crc32-stream/node_modules/events": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/crc32-stream/node_modules/ieee754": { - "version": "1.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/crc32-stream/node_modules/readable-stream": { - "version": "4.5.2", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/create-ecdh": { "version": "4.0.4", "license": "MIT", @@ -10032,8 +11888,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "license": "MIT", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -10183,6 +12040,23 @@ "node": ">=18" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "dev": true, @@ -10400,7 +12274,6 @@ }, "node_modules/diff": { "version": "5.1.0", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -10500,8 +12373,37 @@ "domelementtype": "^2.3.0", "domhandler": "^5.0.3" }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/downlevel-dts": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/downlevel-dts/-/downlevel-dts-0.10.1.tgz", + "integrity": "sha512-bwY63Y0Gfwotcly4vU6rB66m5txvfQzDGd1Gai9E9orqsDIswXKjkdR18Tm6TidnAk9+J5N68a5VMDO2bsQCKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.2", + "shelljs": "^0.8.3", + "typescript": "next" + }, + "bin": { + "downlevel-dts": "index.js" + } + }, + "node_modules/downlevel-dts/node_modules/typescript": { + "version": "5.7.0-dev.20241023", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.0-dev.20241023.tgz", + "integrity": "sha512-HB6rRd9ySnFMoQUuDasWnBtvOg1P4CFG3nRfs2ZvFbenUkxSzoeeZ0PUwJJ7MKJp5zz7uMTZamGt7zdj0tP9YA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, "node_modules/duplexer": { @@ -10561,8 +12463,9 @@ "license": "ISC" }, "node_modules/elliptic": { - "version": "6.5.7", - "license": "MIT", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -11289,13 +13192,6 @@ "through": "^2.3.8" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/eventemitter3": { "version": "4.0.7", "dev": true, @@ -11359,16 +13255,17 @@ } }, "node_modules/express": { - "version": "4.21.0", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, - "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -11382,7 +13279,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -11397,6 +13294,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/array-flatten": { @@ -11434,9 +13335,10 @@ "license": "MIT" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.10", - "dev": true, - "license": "MIT" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true }, "node_modules/express/node_modules/statuses": { "version": "2.0.1", @@ -11484,6 +13386,7 @@ }, "node_modules/fast-fifo": { "version": "1.3.2", + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -11645,274 +13548,508 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/finalhandler/node_modules/statuses": { - "version": "2.0.1", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.2", + "dev": true, + "license": "ISC" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "license": "MIT" + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/geometry-interfaces": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stdin": { + "version": "8.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, + "node_modules/get-stream": { + "version": "5.2.0", "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "pump": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "dev": true, + "node_modules/get-symbol-description": { + "version": "1.0.0", "license": "MIT", "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flatted": { - "version": "3.2.2", + "node_modules/github-from-package": { + "version": "0.0.0", "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true }, - "node_modules/fn.name": { - "version": "1.1.0", - "license": "MIT" + "node_modules/glob": { + "version": "10.3.10", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/follow-redirects": { - "version": "1.15.6", + "node_modules/glob-parent": { + "version": "5.1.2", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "engines": { + "node": ">= 6" } }, - "node_modules/for-each": { - "version": "0.3.3", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "balanced-match": "^1.0.0" } }, - "node_modules/foreground-child": { - "version": "3.1.1", + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.3", "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" + "brace-expansion": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "license": "ISC", "engines": { - "node": ">=14" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "4.0.0", + "node_modules/globals": { + "version": "13.24.0", "dev": true, "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "type-fest": "^0.20.2" }, "engines": { - "node": ">= 6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/forwarded": { - "version": "0.2.0", + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", "dev": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fresh": { - "version": "0.5.2", + "node_modules/globby": { + "version": "11.1.0", "dev": true, "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/from": { - "version": "0.1.7", - "license": "MIT" - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "dev": true, + "node_modules/gopd": { + "version": "1.0.1", "license": "MIT", - "optional": true + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/fs-extra": { - "version": "10.1.0", + "node_modules/got": { + "version": "11.8.5", "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/fs-monkey": { - "version": "1.0.3", + "node_modules/graceful-fs": { + "version": "4.2.11", "dev": true, - "license": "Unlicense" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.2", + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gunzip-maybe": { + "version": "1.4.2", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "dependencies": { + "browserify-zlib": "^0.1.4", + "is-deflate": "^1.0.0", + "is-gzip": "^1.0.0", + "peek-stream": "^1.1.0", + "pumpify": "^1.3.3", + "through2": "^2.0.3" + }, + "bin": { + "gunzip-maybe": "bin.js" } }, - "node_modules/function-bind": { - "version": "1.1.2", + "node_modules/handle-thing": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6" } }, - "node_modules/function.prototype.name": { - "version": "1.1.5", + "node_modules/has": { + "version": "1.0.3", "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "function-bind": "^1.1.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.4.0" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", + "node_modules/has-bigints": { + "version": "1.0.2", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/geometry-interfaces": { - "version": "1.1.4", + "node_modules/has-flag": { + "version": "4.0.0", "dev": true, - "license": "MIT" - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "license": "ISC", + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=8" } }, - "node_modules/get-intrinsic": { - "version": "1.2.4", + "node_modules/has-property-descriptors": { + "version": "1.0.2", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stdin": { - "version": "8.0.0", - "dev": true, + "node_modules/has-proto": { + "version": "1.0.3", "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stream": { - "version": "5.2.0", + "node_modules/has-symbols": { + "version": "1.0.3", "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-symbol-description": { + "node_modules/has-tostringtag": { "version": "1.0.0", "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "has-symbols": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -11921,729 +14058,749 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "dev": true, + "node_modules/hash-base": { + "version": "3.1.0", "license": "MIT", - "optional": true - }, - "node_modules/glob": { - "version": "10.3.10", - "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4" } }, - "node_modules/glob-parent": { - "version": "5.1.2", + "node_modules/hash-sum": { + "version": "2.0.0", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", + "node_modules/he": { + "version": "1.2.0", "dev": true, - "license": "BSD-2-Clause" + "license": "MIT", + "bin": { + "he": "bin/he" + } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", + "node_modules/highlight.js": { + "version": "11.9.0", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.3", + "node_modules/hosted-git-info": { + "version": "4.0.2", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "lru-cache": "^6.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, - "node_modules/globals": { - "version": "13.24.0", + "node_modules/hpack.js": { + "version": "2.1.6", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" } }, - "node_modules/globals/node_modules/type-fest": { - "version": "0.20.2", + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.7", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/globby": { - "version": "11.1.0", + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", "dev": true, "license": "MIT", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/gopd": { - "version": "1.0.1", + "node_modules/html-entities": { + "version": "2.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.8" } }, - "node_modules/got": { - "version": "11.8.5", + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.8.1", + "dev": true, "license": "MIT", "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "node": ">= 0.6" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "license": "ISC" + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "license": "BSD-2-Clause" }, - "node_modules/graphemer": { - "version": "1.4.0", + "node_modules/http-deceiver": { + "version": "1.2.7", "dev": true, "license": "MIT" }, - "node_modules/gunzip-maybe": { - "version": "1.4.2", + "node_modules/http-errors": { + "version": "2.0.0", "dev": true, "license": "MIT", "dependencies": { - "browserify-zlib": "^0.1.4", - "is-deflate": "^1.0.0", - "is-gzip": "^1.0.0", - "peek-stream": "^1.1.0", - "pumpify": "^1.3.3", - "through2": "^2.0.3" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, - "bin": { - "gunzip-maybe": "bin.js" + "engines": { + "node": ">= 0.8" } }, - "node_modules/handle-thing": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/hard-rejection": { - "version": "2.1.0", + "node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "node_modules/has": { - "version": "1.0.3", + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "dev": true, "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.8" } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/http-parser-js": { + "version": "0.5.8", + "dev": true, + "license": "MIT" }, - "node_modules/has-flag": { - "version": "4.0.0", + "node_modules/http-proxy": { + "version": "1.18.1", "dev": true, "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">=8.0.0" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0" + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 6" } }, - "node_modules/has-proto": { - "version": "1.0.3", + "node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "dev": true, "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, "engines": { - "node": ">= 0.4" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } } }, - "node_modules/has-symbols": { - "version": "1.0.3", + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", + "node_modules/http2-wrapper": { + "version": "1.0.3", "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10.19.0" } }, - "node_modules/hash-base": { - "version": "3.1.0", + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=4" + "node": ">= 6" } }, - "node_modules/hash-sum": { - "version": "2.0.0", + "node_modules/human-signals": { + "version": "2.1.0", "dev": true, - "license": "MIT" - }, - "node_modules/hash.js": { - "version": "1.1.7", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" + "node": ">=10.17.0" } }, - "node_modules/he": { - "version": "1.2.0", + "node_modules/husky": { + "version": "9.0.7", "dev": true, "license": "MIT", "bin": { - "he": "bin/he" - } - }, - "node_modules/highlight.js": { - "version": "11.9.0", - "license": "BSD-3-Clause", + "husky": "bin.js" + }, "engines": { - "node": ">=12.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" } }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } + "node_modules/i18n-ts": { + "version": "1.0.5", + "license": "MIT" }, - "node_modules/hosted-git-info": { - "version": "4.0.2", + "node_modules/iconv-lite": { + "version": "0.4.24", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/hpack.js": { - "version": "2.1.6", + "node_modules/icss-utils": { + "version": "5.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.7", + "node_modules/ieee754": { + "version": "1.1.13", + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.1", "dev": true, "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "engines": { + "node": ">= 4" } }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", + "node_modules/immediate": { + "version": "3.0.6", "dev": true, "license": "MIT" }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", + "node_modules/immutable": { + "version": "4.3.0", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.0", "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", + "node_modules/import-local": { + "version": "3.0.3", "dev": true, "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/html-entities": { - "version": "2.3.2", + "node_modules/imurmurhash": { + "version": "0.1.4", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } }, - "node_modules/html-escaper": { - "version": "2.0.2", + "node_modules/indent-string": { + "version": "4.0.0", "dev": true, - "license": "MIT" - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "license": "ISC", "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/http-assert": { - "version": "1.5.0", + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/internal-slot": { + "version": "1.0.3", "license": "MIT", "dependencies": { - "deep-equal": "~1.0.1", - "http-errors": "~1.8.0" + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, - "node_modules/http-assert/node_modules/http-errors": { - "version": "1.8.1", + "node_modules/interpret": { + "version": "3.1.1", "dev": true, "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=10.13.0" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "license": "BSD-2-Clause" - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "dev": true, - "license": "MIT" + "node_modules/ip-regex": { + "version": "4.3.0", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/http-errors": { - "version": "2.0.0", + "node_modules/ipaddr.js": { + "version": "2.0.1", "dev": true, "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, "engines": { - "node": ">= 0.8" + "node": ">= 10" } }, - "node_modules/http-errors/node_modules/depd": { - "version": "2.0.0", - "dev": true, + "node_modules/is": { + "version": "3.3.0", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": "*" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "dev": true, + "node_modules/is-arguments": { + "version": "1.1.1", "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "dev": true, + "node_modules/is-arrayish": { + "version": "0.3.2", "license": "MIT" }, - "node_modules/http-proxy": { - "version": "1.18.1", - "dev": true, + "node_modules/is-bigint": { + "version": "1.0.4", "license": "MIT", "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "has-bigints": "^1.0.1" }, - "engines": { - "node": ">=8.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", + "node_modules/is-binary-path": { + "version": "2.1.0", "dev": true, "license": "MIT", "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "dev": true, + "node_modules/is-boolean-object": { + "version": "1.1.2", "license": "MIT", "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { - "version": "3.0.0", + "node_modules/is-buffer": { + "version": "1.1.6", + "dev": true, + "license": "MIT" + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", "dev": true, "license": "MIT", + "dependencies": { + "builtin-modules": "^3.3.0" + }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/http2-wrapper": { - "version": "1.0.3", + "node_modules/is-callable": { + "version": "1.2.4", "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, "engines": { - "node": ">=10.19.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", + "node_modules/is-core-module": { + "version": "2.9.0", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "has": "^1.0.3" }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/husky": { - "version": "9.0.7", - "dev": true, + "node_modules/is-date-object": { + "version": "1.0.5", "license": "MIT", - "bin": { - "husky": "bin.js" + "dependencies": { + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/typicode" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/i18n-ts": { - "version": "1.0.5", + "node_modules/is-deflate": { + "version": "1.0.0", + "dev": true, "license": "MIT" }, - "node_modules/iconv-lite": { - "version": "0.4.24", + "node_modules/is-directory": { + "version": "0.3.1", "dev": true, "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/icss-utils": { - "version": "5.1.0", + "node_modules/is-docker": { + "version": "2.2.1", "dev": true, - "license": "ISC", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">=8" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ieee754": { - "version": "1.1.13", - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.1", + "node_modules/is-extglob": { + "version": "2.1.1", "dev": true, "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=0.10.0" } }, - "node_modules/immediate": { - "version": "3.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/immutable": { - "version": "4.3.0", - "license": "MIT" + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "dev": true, + "node_modules/is-generator-function": { + "version": "1.0.10", "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/import-local": { - "version": "3.0.3", + "node_modules/is-glob": { + "version": "4.0.3", "dev": true, "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", + "node_modules/is-gzip": { + "version": "1.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.19" + "node": ">=0.10.0" } }, - "node_modules/indent-string": { - "version": "4.0.0", + "node_modules/is-nan": { + "version": "1.3.2", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inflight": { - "version": "1.0.6", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "node_modules/is-negative-zero": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", + "node_modules/is-number": { + "version": "7.0.0", "dev": true, - "license": "ISC", - "optional": true + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } }, - "node_modules/internal-slot": { - "version": "1.0.3", + "node_modules/is-number-object": { + "version": "1.0.7", "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/interpret": { - "version": "3.1.1", + "node_modules/is-path-inside": { + "version": "3.0.3", "dev": true, "license": "MIT", "engines": { - "node": ">=10.13.0" + "node": ">=8" } }, - "node_modules/ip-regex": { - "version": "4.3.0", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/ipaddr.js": { - "version": "2.0.1", + "node_modules/is-plain-object": { + "version": "2.0.4", "dev": true, "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, "engines": { - "node": ">= 10" + "node": ">=0.10.0" } }, - "node_modules/is": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": "*" - } + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "dev": true, + "license": "MIT" }, - "node_modules/is-arguments": { - "version": "1.1.1", + "node_modules/is-promise": { + "version": "2.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.1.4", "license": "MIT", "dependencies": { "call-bind": "^1.0.2", @@ -12656,36 +14813,60 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "license": "MIT" + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/is-bigint": { - "version": "1.0.4", + "node_modules/is-stream": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "dev": true, + "node_modules/is-symbol": { + "version": "1.0.4", "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "has-symbols": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", + "node_modules/is-typed-array": { + "version": "1.1.9", "license": "MIT", "dependencies": { + "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", + "es-abstract": "^1.20.0", + "for-each": "^0.3.3", "has-tostringtag": "^1.0.0" }, "engines": { @@ -12695,936 +14876,1005 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-buffer": { - "version": "1.1.6", - "dev": true, - "license": "MIT" - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", + "node_modules/is-unicode-supported": { + "version": "0.1.0", "dev": true, "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-callable": { + "node_modules/is-url": { "version": "1.2.4", + "license": "MIT" + }, + "node_modules/is-weakref": { + "version": "1.0.2", "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "call-bind": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-core-module": { - "version": "2.9.0", + "node_modules/is-wsl": { + "version": "2.2.0", "dev": true, "license": "MIT", "dependencies": { - "has": "^1.0.3" + "is-docker": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/is-date-object": { - "version": "1.0.5", + "node_modules/is2": { + "version": "2.0.7", "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=v0.10.0" } }, - "node_modules/is-deflate": { + "node_modules/isarray": { "version": "1.0.0", - "dev": true, "license": "MIT" }, - "node_modules/is-directory": { - "version": "0.3.1", + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/is-docker": { - "version": "2.2.1", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, + "license": "BSD-3-Clause", "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-extglob": { - "version": "2.1.1", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "license": "MIT", + "node_modules/istanbul-reports": { + "version": "3.1.6", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "license": "MIT", + "node_modules/jackspeak": { + "version": "2.3.6", + "license": "BlueOak-1.0.0", "dependencies": { - "has-tostringtag": "^1.0.0" + "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">= 0.4" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", + "node_modules/jest-worker": { + "version": "27.5.1", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-gzip": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" } }, - "node_modules/is-nan": { - "version": "1.3.2", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "license": "MIT", + "node_modules/jmespath": { + "version": "0.16.0", + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" - }, + "node": ">= 0.6.0" + } + }, + "node_modules/jose": { + "version": "5.4.1", + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/panva" } }, - "node_modules/is-number": { - "version": "7.0.0", + "node_modules/joycon": { + "version": "3.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">=10" } }, - "node_modules/is-number-object": { - "version": "1.0.7", + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "23.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^3.0.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.7", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.14.2", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/is-path-inside": { - "version": "3.0.3", + "node_modules/jsdom/node_modules/agent-base": { + "version": "7.1.0", "dev": true, "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", + "node_modules/jsdom/node_modules/http-proxy-agent": { + "version": "7.0.0", "dev": true, "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "7.0.2", "dev": true, "license": "MIT", "dependencies": { - "isobject": "^3.0.1" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=0.10.0" + "node": ">= 14" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", + "node_modules/jsesc": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "license": "MIT" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", "dev": true, "license": "MIT" }, - "node_modules/is-promise": { - "version": "2.2.2", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", "dev": true, "license": "MIT" }, - "node_modules/is-regex": { - "version": "1.1.4", + "node_modules/json-schema-to-typescript": { + "version": "13.1.1", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "@bcherny/json-schema-ref-parser": "10.0.5-fork", + "@types/json-schema": "^7.0.11", + "@types/lodash": "^4.14.182", + "@types/prettier": "^2.6.1", + "cli-color": "^2.0.2", + "get-stdin": "^8.0.0", + "glob": "^7.1.6", + "glob-promise": "^4.2.2", + "is-glob": "^4.0.3", + "lodash": "^4.17.21", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "mz": "^2.7.0", + "prettier": "^2.6.2" }, - "engines": { - "node": ">= 0.4" + "bin": { + "json2ts": "dist/src/cli.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=12.0.0" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", + "node_modules/json-schema-to-typescript/node_modules/@types/glob": { + "version": "7.2.0", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/minimatch": "*", + "@types/node": "*" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "license": "MIT", + "node_modules/json-schema-to-typescript/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-string": { - "version": "1.0.7", + "node_modules/json-schema-to-typescript/node_modules/glob-promise": { + "version": "4.2.2", + "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "@types/glob": "^7.1.3" }, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "individual", + "url": "https://github.com/sponsors/ahmadnassri" + }, + "peerDependencies": { + "glob": "^7.1.6" } }, - "node_modules/is-symbol": { + "node_modules/json-schema-to-typescript/node_modules/mkdirp": { "version": "1.0.4", + "dev": true, "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/is-typed-array": { - "version": "1.1.9", + "node_modules/json-schema-to-typescript/node_modules/prettier": { + "version": "2.8.8", + "dev": true, "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0" + "bin": { + "prettier": "bin-prettier.js" }, "engines": { - "node": ">= 0.4" + "node": ">=10.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", "dev": true, "license": "MIT", - "engines": { - "node": ">=10" + "bin": { + "json5": "lib/cli.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=6" } }, - "node_modules/is-url": { - "version": "1.2.4", + "node_modules/jsonc-parser": { + "version": "3.2.0", "license": "MIT" }, - "node_modules/is-weakref": { - "version": "1.0.2", + "node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "universalify": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/is-wsl": { - "version": "2.2.0", + "node_modules/jszip": { + "version": "3.10.1", "dev": true, - "license": "MIT", + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" } }, - "node_modules/is2": { - "version": "2.0.7", + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, "license": "MIT", "dependencies": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - }, - "engines": { - "node": ">=v0.10.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/isarray": { - "version": "1.0.0", + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, "license": "MIT" }, - "node_modules/isexe": { - "version": "2.0.0", - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", + "node_modules/just-clone": { + "version": "6.2.0", + "license": "MIT" + }, + "node_modules/just-extend": { + "version": "4.2.1", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", + "node_modules/keygrip": { + "version": "1.1.0", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "tsscmp": "1.0.6" }, "engines": { - "node": ">=10" + "node": ">= 0.6" } }, - "node_modules/istanbul-reports": { - "version": "3.1.6", + "node_modules/keytar": { + "version": "7.9.0", "dev": true, - "license": "BSD-3-Clause", + "hasInstallScript": true, + "license": "MIT", + "optional": true, "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" } }, - "node_modules/jackspeak": { - "version": "2.3.6", - "license": "BlueOak-1.0.0", + "node_modules/keyv": { + "version": "4.0.3", + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" + } + }, + "node_modules/koa": { + "version": "2.15.3", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" } }, - "node_modules/jest-worker": { - "version": "27.5.1", + "node_modules/koa-compose": { + "version": "4.1.0", "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } + "license": "MIT" }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", + "node_modules/koa-convert": { + "version": "2.0.0", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" + "co": "^4.6.0", + "koa-compose": "^4.1.0" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jmespath": { - "version": "0.16.0", - "license": "Apache-2.0", "engines": { - "node": ">= 0.6.0" + "node": ">= 10" } }, - "node_modules/jose": { - "version": "5.4.1", + "node_modules/koa-morgan": { + "version": "1.0.1", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" + "dependencies": { + "morgan": "^1.6.1" } }, - "node_modules/joycon": { - "version": "3.0.1", + "node_modules/koa-mount": { + "version": "4.0.0", "dev": true, "license": "MIT", + "dependencies": { + "debug": "^4.0.1", + "koa-compose": "^4.1.0" + }, "engines": { - "node": ">=10" + "node": ">= 7.6.0" } }, - "node_modules/js-tokens": { - "version": "4.0.0", + "node_modules/koa-send": { + "version": "5.0.1", "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">= 8" } }, - "node_modules/jsdom": { - "version": "23.0.1", + "node_modules/koa-send/node_modules/http-errors": { + "version": "1.8.1", "dev": true, "license": "MIT", "dependencies": { - "cssstyle": "^3.0.0", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.7", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.3", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.14.2", - "xml-name-validator": "^5.0.0" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^2.11.2" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/jsdom/node_modules/agent-base": { - "version": "7.1.0", + "node_modules/koa-static": { + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.4" + "debug": "^3.1.0", + "koa-send": "^5.0.0" }, "engines": { - "node": ">= 14" + "node": ">= 7.6.0" } }, - "node_modules/jsdom/node_modules/http-proxy-agent": { - "version": "7.0.0", + "node_modules/koa-static/node_modules/debug": { + "version": "3.2.7", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, + "ms": "^2.1.1" + } + }, + "node_modules/koa/node_modules/depd": { + "version": "2.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 14" + "node": ">= 0.8" } }, - "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "7.0.2", + "node_modules/koa/node_modules/http-errors": { + "version": "1.8.1", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 14" + "node": ">= 0.6" } }, - "node_modules/jsesc": { - "version": "3.0.2", + "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/json-buffer": { - "version": "3.0.1", + "node_modules/kuler": { + "version": "2.0.0", "license": "MIT" }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", + "node_modules/launch-editor": { + "version": "2.6.0", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", + "node_modules/leven": { + "version": "3.1.0", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/json-schema-to-typescript": { - "version": "13.1.1", + "node_modules/levn": { + "version": "0.4.1", "dev": true, "license": "MIT", "dependencies": { - "@bcherny/json-schema-ref-parser": "10.0.5-fork", - "@types/json-schema": "^7.0.11", - "@types/lodash": "^4.14.182", - "@types/prettier": "^2.6.1", - "cli-color": "^2.0.2", - "get-stdin": "^8.0.0", - "glob": "^7.1.6", - "glob-promise": "^4.2.2", - "is-glob": "^4.0.3", - "lodash": "^4.17.21", - "minimist": "^1.2.6", - "mkdirp": "^1.0.4", - "mz": "^2.7.0", - "prettier": "^2.6.2" - }, - "bin": { - "json2ts": "dist/src/cli.js" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.8.0" } }, - "node_modules/json-schema-to-typescript/node_modules/@types/glob": { - "version": "7.2.0", + "node_modules/lie": { + "version": "3.3.0", "dev": true, "license": "MIT", "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" + "immediate": "~3.0.5" } }, - "node_modules/json-schema-to-typescript/node_modules/glob": { - "version": "7.2.3", + "node_modules/lines-and-columns": { + "version": "1.2.4", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "4.0.1", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "uc.micro": "^1.0.1" } }, - "node_modules/json-schema-to-typescript/node_modules/glob-promise": { - "version": "4.2.2", + "node_modules/loader-runner": { + "version": "4.2.0", "dev": true, - "license": "MIT", - "dependencies": { - "@types/glob": "^7.1.3" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/ahmadnassri" - }, - "peerDependencies": { - "glob": "^7.1.6" + "license": "MIT", + "engines": { + "node": ">=6.11.5" } }, - "node_modules/json-schema-to-typescript/node_modules/mkdirp": { - "version": "1.0.4", + "node_modules/loader-utils": { + "version": "2.0.4", "dev": true, "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" }, "engines": { - "node": ">=10" + "node": ">=8.9.0" } }, - "node_modules/json-schema-to-typescript/node_modules/prettier": { - "version": "2.8.8", + "node_modules/locate-path": { + "version": "6.0.0", "dev": true, "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" + "dependencies": { + "p-locate": "^5.0.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", + "node_modules/lodash": { + "version": "4.17.21", + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", "dev": true, "license": "MIT" }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", + "node_modules/lodash.merge": { + "version": "4.6.2", "dev": true, "license": "MIT" }, - "node_modules/json5": { - "version": "2.2.3", + "node_modules/log-symbols": { + "version": "4.1.0", "dev": true, "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "license": "MIT" - }, - "node_modules/jsonfile": { - "version": "6.1.0", + "node_modules/logform": { + "version": "2.4.0", "license": "MIT", "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" } }, - "node_modules/jszip": { - "version": "3.10.1", + "node_modules/lowercase-keys": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", "dev": true, - "license": "(MIT OR GPL-3.0-or-later)", + "license": "ISC", "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/jszip/node_modules/readable-stream": { - "version": "2.3.8", + "node_modules/lru-queue": { + "version": "0.1.0", "dev": true, "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "es5-ext": "~0.10.2" } }, - "node_modules/jszip/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/jszip/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, + "node_modules/magic-string": { + "version": "0.30.0", "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" } }, - "node_modules/just-clone": { - "version": "6.2.0", - "license": "MIT" - }, - "node_modules/just-extend": { - "version": "4.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/keygrip": { - "version": "1.1.0", + "node_modules/make-dir": { + "version": "4.0.0", "dev": true, "license": "MIT", "dependencies": { - "tsscmp": "1.0.6" + "semver": "^7.5.3" }, "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/keytar": { - "version": "7.9.0", + "node_modules/make-error": { + "version": "1.3.6", + "dev": true, + "license": "ISC" + }, + "node_modules/map-obj": { + "version": "4.3.0", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "dependencies": { - "node-addon-api": "^4.3.0", - "prebuild-install": "^7.0.1" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/keyv": { - "version": "4.0.3", + "node_modules/map-stream": { + "version": "0.0.7", + "license": "MIT" + }, + "node_modules/markdown-it": { + "version": "13.0.2", "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", + "node_modules/markdown-it/node_modules/entities": { + "version": "3.0.1", + "license": "BSD-2-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/klona": { - "version": "2.0.6", + "node_modules/marked": { + "version": "13.0.1", "dev": true, "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, "engines": { - "node": ">= 8" + "node": ">= 18" } }, - "node_modules/koa": { - "version": "2.15.3", + "node_modules/md5": { + "version": "2.3.0", "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", "license": "MIT", "dependencies": { - "accepts": "^1.3.5", - "cache-content-type": "^1.0.0", - "content-disposition": "~0.5.2", - "content-type": "^1.0.4", - "cookies": "~0.9.0", - "debug": "^4.3.2", - "delegates": "^1.0.0", - "depd": "^2.0.0", - "destroy": "^1.0.4", - "encodeurl": "^1.0.2", - "escape-html": "^1.0.3", - "fresh": "~0.5.2", - "http-assert": "^1.3.0", - "http-errors": "^1.6.3", - "is-generator-function": "^1.0.7", - "koa-compose": "^4.1.0", - "koa-convert": "^2.0.0", - "on-finished": "^2.3.0", - "only": "~0.0.2", - "parseurl": "^1.3.2", - "statuses": "^1.5.0", - "type-is": "^1.6.16", - "vary": "^1.1.2" - }, - "engines": { - "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "node_modules/koa-compose": { - "version": "4.1.0", - "dev": true, + "node_modules/mdurl": { + "version": "1.0.1", "license": "MIT" }, - "node_modules/koa-convert": { - "version": "2.0.0", + "node_modules/media-typer": { + "version": "0.3.0", "dev": true, "license": "MIT", - "dependencies": { - "co": "^4.6.0", - "koa-compose": "^4.1.0" + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.7", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.3" }, "engines": { - "node": ">= 10" + "node": ">= 4.0.0" } }, - "node_modules/koa-morgan": { - "version": "1.0.1", + "node_modules/memoizee": { + "version": "0.4.15", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "morgan": "^1.6.1" + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" } }, - "node_modules/koa-mount": { - "version": "4.0.0", + "node_modules/meow": { + "version": "9.0.0", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.0.1", - "koa-compose": "^4.1.0" + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" }, "engines": { - "node": ">= 7.6.0" - } - }, - "node_modules/koa-send": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "http-errors": "^1.7.3", - "resolve-path": "^1.4.0" + "node": ">=10" }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/koa-send/node_modules/http-errors": { - "version": "1.8.1", + "node_modules/meow/node_modules/decamelize": { + "version": "1.2.0", "dev": true, "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/koa-static": { - "version": "5.0.0", + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.1.0", - "koa-send": "^5.0.0" - }, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">= 7.6.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/koa-static/node_modules/debug": { - "version": "3.2.7", + "node_modules/merge-descriptors": { + "version": "1.0.3", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/koa/node_modules/depd": { + "node_modules/merge-stream": { "version": "2.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "license": "MIT" }, - "node_modules/koa/node_modules/http-errors": { - "version": "1.8.1", + "node_modules/merge2": { + "version": "1.4.1", "dev": true, "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, "engines": { - "node": ">= 0.6" + "node": ">= 8" } }, - "node_modules/koa/node_modules/http-errors/node_modules/depd": { + "node_modules/methods": { "version": "1.1.2", "dev": true, "license": "MIT", @@ -13632,1517 +15882,1719 @@ "node": ">= 0.6" } }, - "node_modules/kuler": { - "version": "2.0.0", + "node_modules/microbuffer": { + "version": "1.0.0", + "dev": true, "license": "MIT" }, - "node_modules/launch-editor": { - "version": "2.6.0", + "node_modules/micromatch": { + "version": "4.0.8", "dev": true, "license": "MIT", "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">= 0.6.3" + "node": ">=8.6" } }, - "node_modules/lazystream/node_modules/readable-stream": { - "version": "2.3.8", + "node_modules/miller-rabin": { + "version": "4.0.1", "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" } }, - "node_modules/lazystream/node_modules/safe-buffer": { - "version": "5.1.2", + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", "license": "MIT" }, - "node_modules/lazystream/node_modules/string_decoder": { - "version": "1.1.1", + "node_modules/mime": { + "version": "1.6.0", + "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" } }, - "node_modules/leven": { - "version": "3.1.0", - "dev": true, + "node_modules/mime-db": { + "version": "1.52.0", "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, + "node_modules/mime-types": { + "version": "2.1.35", "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.6" } }, - "node_modules/lie": { - "version": "3.3.0", + "node_modules/mimic-fn": { + "version": "2.1.0", "dev": true, "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" + "engines": { + "node": ">=6" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, - "node_modules/linkify-it": { - "version": "4.0.1", + "node_modules/mimic-response": { + "version": "1.0.1", "license": "MIT", - "dependencies": { - "uc.micro": "^1.0.1" + "engines": { + "node": ">=4" } }, - "node_modules/loader-runner": { - "version": "4.2.0", + "node_modules/min-indent": { + "version": "1.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">=6.11.5" + "node": ">=4" } }, - "node_modules/loader-utils": { - "version": "2.0.4", - "dev": true, - "license": "MIT", + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "license": "ISC", "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8.9.0" + "node": "*" } }, - "node_modules/locate-path": { - "version": "6.0.0", + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } - }, - "node_modules/lodash": { - "version": "4.17.21", - "license": "MIT" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", + }, + "node_modules/minimist-options/node_modules/arrify": { + "version": "1.0.1", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/log-symbols": { - "version": "4.1.0", + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/logform": { - "version": "2.4.0", + "node_modules/minipass": { + "version": "7.0.4", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", "license": "MIT", "dependencies": { - "@colors/colors": "1.5.0", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/lowercase-keys": { - "version": "2.0.0", + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "dev": true, "license": "MIT", - "engines": { - "node": ">=8" - } + "optional": true }, - "node_modules/lru-cache": { - "version": "6.0.0", + "node_modules/mocha": { + "version": "10.1.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, "engines": { - "node": ">=10" + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" } }, - "node_modules/lru-queue": { - "version": "0.1.0", + "node_modules/mocha-junit-reporter": { + "version": "2.2.1", "dev": true, "license": "MIT", "dependencies": { - "es5-ext": "~0.10.2" + "debug": "^4.3.4", + "md5": "^2.3.0", + "mkdirp": "^3.0.0", + "strip-ansi": "^6.0.1", + "xml": "^1.0.1" + }, + "peerDependencies": { + "mocha": ">=2.2.5" } }, - "node_modules/magic-string": { - "version": "0.30.0", + "node_modules/mocha-junit-reporter/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/make-dir": { - "version": "4.0.0", + "node_modules/mocha-junit-reporter/node_modules/mkdirp": { + "version": "3.0.1", "dev": true, "license": "MIT", - "dependencies": { - "semver": "^7.5.3" + "bin": { + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, - "node_modules/map-obj": { - "version": "4.3.0", + "node_modules/mocha-junit-reporter/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/map-stream": { - "version": "0.0.7", - "license": "MIT" - }, - "node_modules/markdown-it": { - "version": "13.0.2", + "node_modules/mocha-multi-reporters": { + "version": "1.5.1", + "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1", - "entities": "~3.0.1", - "linkify-it": "^4.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "debug": "^4.1.1", + "lodash": "^4.17.15" }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "3.0.1", - "license": "BSD-2-Clause", "engines": { - "node": ">=0.12" + "node": ">=6.0.0" }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "peerDependencies": { + "mocha": ">=3.1.2" } }, - "node_modules/marked": { - "version": "13.0.1", + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", "dev": true, "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/md5": { - "version": "2.3.0", + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" + "engines": { + "node": ">=0.3.1" } }, - "node_modules/md5.js": { - "version": "1.3.5", - "license": "MIT", + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "dev": true, + "license": "ISC", "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mdurl": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/media-typer": { - "version": "0.3.0", + "node_modules/mocha/node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/memfs": { - "version": "3.4.7", + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", "dev": true, - "license": "Unlicense", + "license": "ISC", "dependencies": { - "fs-monkey": "^1.0.3" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 4.0.0" + "node": "*" } }, - "node_modules/memoizee": { - "version": "0.4.15", + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", "dev": true, "license": "ISC", "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/meow": { - "version": "9.0.0", + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", "dev": true, "license": "MIT", "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" + "has-flag": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/meow/node_modules/decamelize": { - "version": "1.2.0", + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", "dev": true, "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", + "node_modules/morgan": { + "version": "1.10.0", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/merge-descriptors": { - "version": "1.0.3", + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/merge-stream": { + "node_modules/morgan/node_modules/depd": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/morgan/node_modules/ms": { "version": "2.0.0", "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", "dev": true, "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, "engines": { - "node": ">= 8" + "node": ">= 0.8" } }, - "node_modules/methods": { - "version": "1.1.2", + "node_modules/mri": { + "version": "1.2.0", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=4" } }, - "node_modules/microbuffer": { - "version": "1.0.0", - "dev": true, + "node_modules/ms": { + "version": "2.1.2", "license": "MIT" }, - "node_modules/micromatch": { - "version": "4.0.8", + "node_modules/multicast-dns": { + "version": "7.2.5", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" }, - "engines": { - "node": ">=8.6" + "bin": { + "multicast-dns": "cli.js" } }, - "node_modules/miller-rabin": { - "version": "4.0.1", + "node_modules/mute-stream": { + "version": "0.0.8", + "dev": true, + "license": "ISC" + }, + "node_modules/mvdan-sh": { + "version": "0.10.1", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/mz": { + "version": "2.7.0", + "dev": true, "license": "MIT", "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" } }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "license": "MIT" - }, - "node_modules/mime": { - "version": "1.6.0", + "node_modules/nanoid": { + "version": "3.3.3", "dev": true, "license": "MIT", "bin": { - "mime": "cli.js" + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=4" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/mime-db": { - "version": "1.52.0", + "node_modules/napi-build-utils": { + "version": "1.0.2", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "optional": true }, - "node_modules/mime-types": { - "version": "2.1.35", + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/neatequal": { + "version": "1.0.0", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "varstream": "^0.3.2" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", + "node_modules/negotiator": { + "version": "0.6.3", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=4" + "node_modules/neo-async": { + "version": "2.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/next-tick": { + "version": "1.1.0", + "dev": true, + "license": "ISC" + }, + "node_modules/nise": { + "version": "5.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" } }, - "node_modules/min-indent": { - "version": "1.0.1", + "node_modules/node-abi": { + "version": "3.45.0", "dev": true, "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "license": "ISC" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "license": "MIT" + "node_modules/node-addon-api": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "optional": true }, - "node_modules/minimatch": { - "version": "3.1.2", - "license": "ISC", + "node_modules/node-fetch": { + "version": "2.7.0", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "whatwg-url": "^5.0.0" }, "engines": { - "node": "*" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/minimist": { - "version": "1.2.8", + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" } }, - "node_modules/minimist-options": { - "version": "4.1.0", + "node_modules/node-releases": { + "version": "2.0.14", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" }, "engines": { - "node": ">= 6" + "node": ">=10" } }, - "node_modules/minimist-options/node_modules/arrify": { - "version": "1.0.1", + "node_modules/normalize-path": { + "version": "3.0.0", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/minimist-options/node_modules/is-plain-obj": { - "version": "1.1.0", - "dev": true, + "node_modules/normalize-url": { + "version": "6.1.0", "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/minipass": { - "version": "7.0.4", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mkdirp": { - "version": "0.5.6", + "node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.6" + "path-key": "^3.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=8" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", + "node_modules/nth-check": { + "version": "2.1.1", "dev": true, - "license": "MIT", - "optional": true + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } }, - "node_modules/mocha": { - "version": "10.1.0", + "node_modules/nunjucks": { + "version": "3.2.4", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" }, "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" + "nunjucks-precompile": "bin/precompile" }, "engines": { - "node": ">= 14.0.0" + "node": ">= 6.9.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/mocha-junit-reporter": { - "version": "2.2.1", + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "md5": "^2.3.0", - "mkdirp": "^3.0.0", - "strip-ansi": "^6.0.1", - "xml": "^1.0.1" - }, - "peerDependencies": { - "mocha": ">=2.2.5" + "engines": { + "node": ">= 6" } }, - "node_modules/mocha-junit-reporter/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/nwsapi": { + "version": "2.2.7", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/mocha-junit-reporter/node_modules/mkdirp": { - "version": "3.0.1", - "dev": true, + "node_modules/object-inspect": { + "version": "1.13.2", "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha-junit-reporter/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/object-is": { + "version": "1.1.5", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha-multi-reporters": { - "version": "1.5.1", - "dev": true, + "node_modules/object-keys": { + "version": "1.1.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", "license": "MIT", "dependencies": { - "debug": "^4.1.1", - "lodash": "^4.17.15" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=6.0.0" + "node": ">= 0.4" }, - "peerDependencies": { - "mocha": ">=3.1.2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", + "node_modules/obuf": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/mocha/node_modules/diff": { - "version": "5.0.0", + "node_modules/on-headers": { + "version": "1.0.2", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=0.3.1" + "node": ">= 0.8" } }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "dev": true, + "node_modules/once": { + "version": "1.4.0", "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "wrappy": "1" } }, - "node_modules/mocha/node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, + "node_modules/one-time": { + "version": "1.0.0", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "fn.name": "1.x.x" } }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", + "node_modules/onetime": { + "version": "5.1.2", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" + "node": ">=6" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" + "node_modules/only": { + "version": "0.0.2", + "dev": true }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", + "node_modules/open": { + "version": "8.4.0", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", + "node_modules/optionator": { + "version": "0.9.3", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" }, "engines": { - "node": ">=10" + "node": ">= 0.8.0" } }, - "node_modules/morgan": { - "version": "1.10.0", - "dev": true, + "node_modules/os-browserify": { + "version": "0.3.0", + "license": "MIT" + }, + "node_modules/p-cancelable": { + "version": "2.1.1", "license": "MIT", - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", + "node_modules/p-limit": { + "version": "3.1.0", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/morgan/node_modules/depd": { - "version": "2.0.0", + "node_modules/p-locate": { + "version": "5.0.0", "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", + "node_modules/p-retry": { + "version": "4.6.1", "dev": true, "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "@types/retry": "^0.12.0", + "retry": "^0.13.1" }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/mri": { - "version": "1.2.0", + "node_modules/p-try": { + "version": "2.2.0", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/ms": { - "version": "2.1.2", - "license": "MIT" + "node_modules/pako": { + "version": "1.0.11", + "dev": true, + "license": "(MIT AND Zlib)" }, - "node_modules/multicast-dns": { - "version": "7.2.5", + "node_modules/parent-module": { + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" + "callsites": "^3.0.0" }, - "bin": { - "multicast-dns": "cli.js" + "engines": { + "node": ">=6" } }, - "node_modules/mute-stream": { - "version": "0.0.8", - "dev": true, - "license": "ISC" - }, - "node_modules/mvdan-sh": { - "version": "0.10.1", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/mz": { - "version": "2.7.0", - "dev": true, - "license": "MIT", + "node_modules/parse-asn1": { + "version": "5.1.6", + "license": "ISC", "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, - "node_modules/nanoid": { - "version": "3.3.3", + "node_modules/parse-json": { + "version": "5.2.0", "dev": true, "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "dev": true, + "node_modules/parse-node-version": { + "version": "1.0.1", "license": "MIT", - "optional": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" + "engines": { + "node": ">= 0.10" + } }, - "node_modules/neatequal": { - "version": "1.0.0", + "node_modules/parse-semver": { + "version": "1.1.1", "dev": true, "license": "MIT", "dependencies": { - "varstream": "^0.3.2" + "semver": "^5.1.0" } }, - "node_modules/negotiator": { - "version": "0.6.3", + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "license": "ISC", + "bin": { + "semver": "bin/semver" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "dev": true, + "node_modules/parse-srcset": { + "version": "1.0.2", "license": "MIT" }, - "node_modules/next-tick": { - "version": "1.1.0", - "dev": true, - "license": "ISC" - }, - "node_modules/nise": { - "version": "5.1.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.8.3", - "@sinonjs/fake-timers": ">=5", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "node_modules/node-abi": { - "version": "3.45.0", + "node_modules/parse5": { + "version": "7.1.2", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "semver": "^7.3.5" + "entities": "^4.4.0" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/node-addon-api": { - "version": "4.3.0", + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", "dev": true, "license": "MIT", - "optional": true - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "license": "MIT", "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/node-forge": { - "version": "1.3.1", + "node_modules/parseurl": { + "version": "1.3.3", "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", + "license": "MIT", "engines": { - "node": ">= 6.13.0" + "node": ">= 0.8" } }, - "node_modules/node-releases": { - "version": "2.0.14", - "dev": true, + "node_modules/path-browserify": { + "version": "1.0.1", "license": "MIT" }, - "node_modules/normalize-package-data": { - "version": "3.0.3", + "node_modules/path-exists": { + "version": "4.0.0", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/normalize-path": { - "version": "3.0.0", + "node_modules/path-is-absolute": { + "version": "1.0.1", "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "6.1.0", + "node_modules/path-key": { + "version": "3.1.1", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.1.0", + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-to-regexp": { + "version": "1.9.0", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.0.0" - }, + "isarray": "0.0.1" + } + }, + "node_modules/path-to-regexp/node_modules/isarray": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/pause-stream": { + "version": "0.0.11", + "license": [ + "MIT", + "Apache2" + ], "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "through": "~2.3" } }, - "node_modules/nunjucks": { - "version": "3.2.4", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/pbkdf2": { + "version": "3.1.2", + "license": "MIT", "dependencies": { - "a-sync-waterfall": "^1.0.0", - "asap": "^2.0.3", - "commander": "^5.1.0" - }, - "bin": { - "nunjucks-precompile": "bin/precompile" + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" }, "engines": { - "node": ">= 6.9.0" - }, - "peerDependencies": { - "chokidar": "^3.3.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "node": ">=0.12" } }, - "node_modules/nunjucks/node_modules/commander": { - "version": "5.1.0", + "node_modules/peek-stream": { + "version": "1.1.3", "dev": true, "license": "MIT", - "engines": { - "node": ">= 6" + "dependencies": { + "buffer-from": "^1.0.0", + "duplexify": "^3.5.0", + "through2": "^2.0.3" } }, - "node_modules/nwsapi": { - "version": "2.2.7", + "node_modules/pend": { + "version": "1.2.0", "dev": true, "license": "MIT" }, - "node_modules/object-assign": { - "version": "4.1.1", + "node_modules/picocolors": { + "version": "1.0.1", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/object-inspect": { - "version": "1.13.2", + "node_modules/pkg-dir": { + "version": "4.2.0", + "dev": true, "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/object-is": { - "version": "1.1.5", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "p-locate": "^4.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object-keys": { - "version": "1.1.1", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/object.assign": { - "version": "4.1.4", - "license": "MIT", + "node_modules/playwright": { + "version": "1.44.0", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" + "playwright-core": "1.44.0" + }, + "bin": { + "playwright": "cli.js" }, "engines": { - "node": ">= 0.4" + "node": ">=16" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "fsevents": "2.3.2" } }, - "node_modules/obuf": { - "version": "1.1.2", + "node_modules/playwright-core": { + "version": "1.44.0", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } }, - "node_modules/on-finished": { - "version": "2.4.1", + "node_modules/pluralize": { + "version": "8.0.0", "dev": true, "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.12.0" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "dev": true, + "node_modules/portfinder/node_modules/async": { + "version": "2.6.4", "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "lodash": "^4.17.14" } }, - "node_modules/once": { - "version": "1.4.0", - "license": "ISC", + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "license": "MIT", "dependencies": { - "wrappy": "1" + "ms": "^2.1.1" } }, - "node_modules/one-time": { - "version": "1.0.0", + "node_modules/postcss": { + "version": "8.4.33", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "fn.name": "1.x.x" + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/onetime": { - "version": "5.1.2", + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.4", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" }, "engines": { - "node": ">=6" + "node": "^10 || ^12 || >= 14" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/only": { - "version": "0.0.2", - "dev": true + "node_modules/postcss-modules-scope": { + "version": "3.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } }, - "node_modules/open": { - "version": "8.4.0", + "node_modules/postcss-modules-values": { + "version": "4.0.0", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "icss-utils": "^5.0.0" }, "engines": { - "node": ">=12" + "node": "^10 || ^12 || >= 14" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/optionator": { - "version": "0.9.3", + "node_modules/postcss-selector-parser": { + "version": "6.0.15", "dev": true, "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">=4" } }, - "node_modules/os-browserify": { - "version": "0.3.0", + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "dev": true, "license": "MIT" }, - "node_modules/p-cancelable": { - "version": "2.1.1", + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.7", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/p-limit": { - "version": "3.1.0", + "node_modules/prebuild-install": { + "version": "7.1.1", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "yocto-queue": "^0.1.0" + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "5.0.0", + "node_modules/prelude-ls": { + "version": "1.2.1", "dev": true, "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/p-retry": { - "version": "4.6.1", + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", "dev": true, "license": "MIT", "dependencies": { - "@types/retry": "^0.12.0", - "retry": "^0.13.1" + "fast-diff": "^1.1.2" }, "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/p-try": { - "version": "2.2.0", + "node_modules/prettier-plugin-sh": { + "version": "0.14.0", "dev": true, "license": "MIT", + "dependencies": { + "mvdan-sh": "^0.10.1", + "sh-syntax": "^0.4.1" + }, "engines": { - "node": ">=6" + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" } }, - "node_modules/pako": { - "version": "1.0.11", - "dev": true, - "license": "(MIT AND Zlib)" - }, - "node_modules/parent-module": { - "version": "1.0.1", + "node_modules/pretty-quick": { + "version": "4.0.0", "dev": true, "license": "MIT", "dependencies": { - "callsites": "^3.0.0" + "execa": "^5.1.1", + "find-up": "^5.0.0", + "ignore": "^5.3.0", + "mri": "^1.2.0", + "picocolors": "^1.0.0", + "picomatch": "^3.0.1", + "tslib": "^2.6.2" + }, + "bin": { + "pretty-quick": "lib/cli.mjs" }, "engines": { - "node": ">=6" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "license": "ISC", - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" + "node": ">=14" + }, + "peerDependencies": { + "prettier": "^3.0.0" } }, - "node_modules/parse-json": { - "version": "5.2.0", + "node_modules/pretty-quick/node_modules/picomatch": { + "version": "3.0.1", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", + }, + "node_modules/prismjs": { + "version": "1.29.0", "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=6" } }, - "node_modules/parse-semver": { - "version": "1.1.1", + "node_modules/private": { + "version": "0.1.8", "dev": true, "license": "MIT", - "dependencies": { - "semver": "^5.1.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/parse-semver/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "node_modules/process": { + "version": "0.11.10", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" } }, - "node_modules/parse-srcset": { - "version": "1.0.2", + "node_modules/process-nextick-args": { + "version": "2.0.1", "license": "MIT" }, - "node_modules/parse5": { - "version": "7.1.2", + "node_modules/proxy-addr": { + "version": "2.0.7", "dev": true, "license": "MIT", "dependencies": { - "entities": "^4.4.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">= 0.10" } }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", "dev": true, "license": "MIT", - "dependencies": { - "domhandler": "^5.0.2", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">= 0.10" } }, - "node_modules/parseurl": { - "version": "1.3.3", + "node_modules/psl": { + "version": "1.9.0", "dev": true, + "license": "MIT" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "node_modules/path-browserify": { - "version": "1.0.1", + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", "license": "MIT" }, - "node_modules/path-exists": { - "version": "4.0.0", + "node_modules/pump": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/path-key": { - "version": "3.1.1", + "node_modules/punycode": { + "version": "2.3.1", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/path-parse": { - "version": "1.0.7", + "node_modules/qs": { + "version": "6.13.0", "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.10.1", - "license": "BlueOak-1.0.0", + "license": "BSD-3-Clause", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "side-channel": "^1.0.6" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=0.6" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.1.0", - "license": "ISC", + "node_modules/querystring": { + "version": "0.2.0", "engines": { - "node": "14 || >=16.14" + "node": ">=0.4.x" } }, - "node_modules/path-to-regexp": { - "version": "1.9.0", + "node_modules/querystringify": { + "version": "2.2.0", "dev": true, - "license": "MIT", - "dependencies": { - "isarray": "0.0.1" - } + "license": "MIT" }, - "node_modules/path-to-regexp/node_modules/isarray": { - "version": "0.0.1", + "node_modules/queue-microtask": { + "version": "1.2.3", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT" }, - "node_modules/path-type": { - "version": "4.0.0", + "node_modules/queue-tick": { + "version": "1.0.1", "dev": true, + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pause-stream": { - "version": "0.0.11", - "license": [ - "MIT", - "Apache2" - ], + "node_modules/randombytes": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "through": "~2.3" + "safe-buffer": "^5.1.0" } }, - "node_modules/pbkdf2": { - "version": "3.1.2", + "node_modules/randomfill": { + "version": "1.0.4", "license": "MIT", "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.12" + "node": ">= 0.6" } }, - "node_modules/peek-stream": { - "version": "1.1.3", + "node_modules/raw-body": { + "version": "2.5.2", "dev": true, "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "duplexify": "^3.5.0", - "through2": "^2.0.3" + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/pend": { - "version": "1.2.0", + "node_modules/rc": { + "version": "1.2.8", "dev": true, - "license": "MIT" + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } }, - "node_modules/picocolors": { - "version": "1.0.1", - "license": "ISC" + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/picomatch": { - "version": "2.3.1", + "node_modules/read": { + "version": "1.0.7", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", "dev": true, "license": "MIT", - "engines": { - "node": ">=8.6" + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">=8" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", + "node_modules/read-pkg-up": { + "version": "7.0.1", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^4.0.0" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-dir/node_modules/find-up": { + "node_modules/read-pkg-up/node_modules/find-up": { "version": "4.1.0", "dev": true, "license": "MIT", @@ -15154,7 +17606,7 @@ "node": ">=8" } }, - "node_modules/pkg-dir/node_modules/locate-path": { + "node_modules/read-pkg-up/node_modules/locate-path": { "version": "5.0.0", "dev": true, "license": "MIT", @@ -15165,7 +17617,7 @@ "node": ">=8" } }, - "node_modules/pkg-dir/node_modules/p-limit": { + "node_modules/read-pkg-up/node_modules/p-limit": { "version": "2.3.0", "dev": true, "license": "MIT", @@ -15179,7 +17631,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-dir/node_modules/p-locate": { + "node_modules/read-pkg-up/node_modules/p-locate": { "version": "4.1.0", "dev": true, "license": "MIT", @@ -15190,451 +17642,466 @@ "node": ">=8" } }, - "node_modules/playwright": { - "version": "1.44.0", + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", "dev": true, - "license": "Apache-2.0", + "license": "ISC" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "playwright-core": "1.44.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "fsevents": "2.3.2" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, - "node_modules/playwright-core": { - "version": "1.44.0", + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=16" + "semver": "bin/semver" } }, - "node_modules/pluralize": { - "version": "8.0.0", + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", "dev": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/portfinder": { - "version": "1.0.32", + "node_modules/readable-stream": { + "version": "3.6.2", "license": "MIT", "dependencies": { - "async": "^2.6.4", - "debug": "^3.2.7", - "mkdirp": "^0.5.6" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">= 0.12.0" + "node": ">= 6" } }, - "node_modules/portfinder/node_modules/async": { - "version": "2.6.4", + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, "license": "MIT", "dependencies": { - "lodash": "^4.17.14" + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.7", + "node_modules/readline-sync": { + "version": "1.4.10", + "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/postcss": { - "version": "8.4.33", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/recast": { + "version": "0.11.23", + "dev": true, "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "ast-types": "0.9.6", + "esprima": "~3.1.0", + "private": "~0.1.5", + "source-map": "~0.5.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">= 0.8" } }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", + "node_modules/recast/node_modules/ast-types": { + "version": "0.9.6", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">= 0.8" + } + }, + "node_modules/recast/node_modules/esprima": { + "version": "3.1.3", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, - "peerDependencies": { - "postcss": "^8.1.0" + "engines": { + "node": ">=4" } }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.4", + "node_modules/recast/node_modules/source-map": { + "version": "0.5.7", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", "dev": true, "license": "MIT", "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" + "resolve": "^1.20.0" }, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">= 10.13.0" } }, - "node_modules/postcss-modules-scope": { - "version": "3.1.1", + "node_modules/redent": { + "version": "3.0.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.4" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" }, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">=8" } }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "license": "MIT", "dependencies": { - "icss-utils": "^5.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">= 0.4" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/postcss-selector-parser": { - "version": "6.0.15", + "node_modules/regjsparser": { + "version": "0.10.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "license": "ISC" + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/request-light": { + "version": "0.2.5", + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "vscode-nls": "^4.1.1" + } + }, + "node_modules/request-light/node_modules/agent-base": { + "version": "4.3.0", "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "es6-promisify": "^5.0.0" }, "engines": { - "node": ">=4" + "node": ">= 4.0.0" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "dev": true, - "license": "MIT" + "node_modules/request-light/node_modules/debug": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.7", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/request-light/node_modules/http-proxy-agent": { + "version": "2.1.0", "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "agent-base": "4", + "debug": "3.1.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">= 4.5.0" } }, - "node_modules/prebuild-install": { - "version": "7.1.1", - "dev": true, + "node_modules/request-light/node_modules/https-proxy-agent": { + "version": "2.2.4", "license": "MIT", - "optional": true, "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" + "agent-base": "^4.3.0", + "debug": "^3.1.0" }, "engines": { - "node": ">=10" + "node": ">= 4.5.0" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, + "node_modules/request-light/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/request-light/node_modules/vscode-nls": { + "version": "4.1.2", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=0.10.0" } }, - "node_modules/prettier": { - "version": "3.3.2", + "node_modules/require-from-string": { + "version": "2.0.2", "dev": true, "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/prettier-linter-helpers": { + "node_modules/requires-port": { "version": "1.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } + "license": "MIT" }, - "node_modules/prettier-plugin-sh": { - "version": "0.14.0", + "node_modules/resolve": { + "version": "1.22.1", "dev": true, "license": "MIT", "dependencies": { - "mvdan-sh": "^0.10.1", - "sh-syntax": "^0.4.1" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": ">=16.0.0" + "bin": { + "resolve": "bin/resolve" }, "funding": { - "url": "https://opencollective.com/unts" - }, - "peerDependencies": { - "prettier": "^3.0.3" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pretty-quick": { - "version": "4.0.0", + "node_modules/resolve-alpn": { + "version": "1.2.1", + "license": "MIT" + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.1.1", - "find-up": "^5.0.0", - "ignore": "^5.3.0", - "mri": "^1.2.0", - "picocolors": "^1.0.0", - "picomatch": "^3.0.1", - "tslib": "^2.6.2" - }, - "bin": { - "pretty-quick": "lib/cli.mjs" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "prettier": "^3.0.0" + "node": ">=8" } }, - "node_modules/pretty-quick/node_modules/picomatch": { - "version": "3.0.1", + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=8" } }, - "node_modules/prismjs": { - "version": "1.29.0", + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/private": { - "version": "0.1.8", + "node_modules/resolve-path": { + "version": "1.4.0", "dev": true, "license": "MIT", + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/process": { - "version": "0.11.10", + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "dev": true, "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, "engines": { - "node": ">= 0.6.0" + "node": ">= 0.6" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "license": "MIT" + "node_modules/resolve-path/node_modules/inherits": { + "version": "2.0.3", + "dev": true, + "license": "ISC" }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "dev": true, + "license": "ISC" }, - "node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "engines": { - "node": ">= 4" + "node_modules/responselike": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", + "node_modules/retry": { + "version": "0.13.1", "dev": true, "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, "engines": { - "node": ">= 0.10" + "node": ">= 4" } }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", + "node_modules/reusify": { + "version": "1.0.4", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, - "node_modules/ps-list": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-8.1.1.tgz", - "integrity": "sha512-OPS9kEJYVmiO48u/B9qneqhkMvgCxT+Tm28VCEJpheTpl8cJ0ffZRRNgS5mrQRTrX5yRTpaJ+hRDeefXYmmorQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/psl": { - "version": "1.9.0", + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", "dev": true, - "license": "MIT" - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "license": "MIT", + "license": "ISC", "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.0", + "node_modules/ripemd160": { + "version": "2.0.2", "license": "MIT", "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, - "node_modules/pumpify": { - "version": "1.5.1", + "node_modules/rrweb-cssom": { + "version": "0.6.0", "dev": true, - "license": "MIT", - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } + "license": "MIT" }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", + "node_modules/run-parallel": { + "version": "1.2.0", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=6" + "queue-microtask": "^1.2.2" } }, - "node_modules/qs": { - "version": "6.13.0", + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "Apache-2.0", "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" + "tslib": "^1.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystring": { - "version": "0.2.0", "engines": { - "node": ">=0.4.x" + "npm": ">=2.0.0" } }, - "node_modules/querystringify": { - "version": "2.2.0", + "node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "MIT" + "license": "0BSD" }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "dev": true, + "node_modules/safe-buffer": { + "version": "5.2.1", "funding": [ { "type": "github", @@ -15651,337 +18118,447 @@ ], "license": "MIT" }, - "node_modules/queue-tick": { - "version": "1.0.1", + "node_modules/safe-stable-stringify": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", "license": "MIT" }, - "node_modules/quick-lru": { - "version": "5.1.1", + "node_modules/sanitize-html": { + "version": "2.13.0", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/sanitize-html/node_modules/is-plain-object": { + "version": "5.0.0", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=0.10.0" + } + }, + "node_modules/sass": { + "version": "1.69.5", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.2.tgz", + "integrity": "sha512-Ll6iXZ1EYwYT19SqW4mSBb76vSSi8JgzElmzIerhEGgzB5hRjDQIWsPmuk1UrAXkR16KJHqVY0eH+5/uw9Tmfw==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/randombytes": { - "version": "2.1.0", - "license": "MIT", + "node_modules/sax": { + "version": "1.2.1", + "license": "ISC" + }, + "node_modules/saxes": { + "version": "6.0.0", + "dev": true, + "license": "ISC", "dependencies": { - "safe-buffer": "^5.1.0" + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" } }, - "node_modules/randomfill": { - "version": "1.0.4", + "node_modules/schema-utils": { + "version": "3.3.0", + "dev": true, "license": "MIT", "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/range-parser": { - "version": "1.2.1", + "node_modules/select-hose": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.1.1", "dev": true, "license": "MIT", + "dependencies": { + "node-forge": "^1" + }, "engines": { - "node": ">= 0.6" + "node": ">=10" } }, - "node_modules/raw-body": { - "version": "2.5.2", + "node_modules/semver": { + "version": "7.6.2", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/depd": { + "version": "2.0.0", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/rc": { - "version": "1.2.8", + "node_modules/send/node_modules/ms": { + "version": "2.1.3", "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "optional": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } + "license": "MIT" }, - "node_modules/rc/node_modules/strip-json-comments": { + "node_modules/send/node_modules/statuses": { "version": "2.0.1", "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/read": { - "version": "1.0.7", + "node_modules/serialize-javascript": { + "version": "6.0.0", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" + "randombytes": "^2.1.0" } }, - "node_modules/read-pkg": { - "version": "5.2.0", + "node_modules/serve-index": { + "version": "1.9.1", "dev": true, "license": "MIT", "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/read-pkg-up": { - "version": "7.0.1", + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "ms": "2.0.0" } }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" }, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-static": { + "version": "1.16.2", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", "dev": true, "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, + "node_modules/set-function-length": { + "version": "1.2.2", "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", + "node_modules/setimmediate": { + "version": "1.0.5", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } + "license": "MIT" }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", + "node_modules/setprototypeof": { + "version": "1.2.0", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } + "license": "ISC" }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", + "node_modules/sh-syntax": { + "version": "0.4.2", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/readdir-glob": { - "version": "1.1.3", - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.1.0" - } - }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, - "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", - "license": "ISC", + "node_modules/sha.js": { + "version": "2.4.11", + "license": "(MIT AND BSD-3-Clause)", "dependencies": { - "brace-expansion": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" }, - "engines": { - "node": ">=10" + "bin": { + "sha.js": "bin.js" } }, - "node_modules/readdirp": { - "version": "3.6.0", + "node_modules/shallow-clone": { + "version": "3.0.1", "dev": true, "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "kind-of": "^6.0.2" }, "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readline-sync": { - "version": "1.4.10", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/recast": { - "version": "0.11.23", - "dev": true, + "node_modules/shebang-command": { + "version": "2.0.0", "license": "MIT", "dependencies": { - "ast-types": "0.9.6", - "esprima": "~3.1.0", - "private": "~0.1.5", - "source-map": "~0.5.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/recast/node_modules/ast-types": { - "version": "0.9.6", - "dev": true, + "node_modules/shebang-regex": { + "version": "3.0.0", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/recast/node_modules/esprima": { - "version": "3.1.3", + "node_modules/shell-quote": { + "version": "1.8.1", "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/recast/node_modules/source-map": { - "version": "0.5.7", + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dev": true, "license": "BSD-3-Clause", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/rechoir": { - "version": "0.8.0", + "node_modules/shelljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "resolve": "^1.20.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 10.13.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/redent": { - "version": "3.0.0", + "node_modules/shelljs/node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true, "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 0.10" } }, - "node_modules/regexp-tree": { - "version": "0.1.27", + "node_modules/shelljs/node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", "dev": true, - "license": "MIT", - "bin": { - "regexp-tree": "bin/regexp-tree" + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", + "node_modules/side-channel": { + "version": "1.0.6", "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" }, "engines": { "node": ">= 0.4" @@ -15990,3477 +18567,3682 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regjsparser": { - "version": "0.10.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", + "node_modules/signal-exit": { + "version": "3.0.7", "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", "license": "ISC" }, - "node_modules/replace-ext": { + "node_modules/simple-concat": { "version": "1.0.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/request-light": { - "version": "0.2.5", - "license": "MIT", - "dependencies": { - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "vscode-nls": "^4.1.1" - } + "optional": true }, - "node_modules/request-light/node_modules/agent-base": { - "version": "4.3.0", + "node_modules/simple-get": { + "version": "4.0.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", + "optional": true, "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, - "node_modules/request-light/node_modules/debug": { - "version": "3.1.0", + "node_modules/simple-swizzle": { + "version": "0.2.2", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "is-arrayish": "^0.3.1" } }, - "node_modules/request-light/node_modules/http-proxy-agent": { - "version": "2.1.0", - "license": "MIT", + "node_modules/sinon": { + "version": "14.0.0", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "agent-base": "4", - "debug": "3.1.0" + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^6.1.1", + "diff": "^5.0.0", + "nise": "^5.1.1", + "supports-color": "^7.2.0" }, - "engines": { - "node": ">= 4.5.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" } }, - "node_modules/request-light/node_modules/https-proxy-agent": { - "version": "2.2.4", - "license": "MIT", + "node_modules/sinon/node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/request-light/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/request-light/node_modules/vscode-nls": { - "version": "4.1.2", - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "@sinonjs/commons": "^1.7.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", + "node_modules/slash": { + "version": "3.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.1", + "node_modules/sockjs": { + "version": "0.3.24", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "license": "MIT" - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", "dev": true, "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", + "node_modules/source-list-map": { + "version": "2.0.1", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "license": "BSD-3-Clause", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", + "node_modules/source-map-js": { + "version": "1.0.2", + "license": "BSD-3-Clause", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/resolve-path": { - "version": "1.4.0", + "node_modules/source-map-support": { + "version": "0.5.21", "dev": true, "license": "MIT", "dependencies": { - "http-errors": "~1.6.2", - "path-is-absolute": "1.0.1" - }, - "engines": { - "node": ">= 0.8" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/resolve-path/node_modules/http-errors": { - "version": "1.6.3", + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.1.1", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/resolve-path/node_modules/inherits": { - "version": "2.0.3", + "node_modules/spdx-exceptions": { + "version": "2.3.0", "dev": true, - "license": "ISC" + "license": "CC-BY-3.0" }, - "node_modules/resolve-path/node_modules/setprototypeof": { - "version": "1.1.0", + "node_modules/spdx-expression-parse": { + "version": "3.0.1", "dev": true, - "license": "ISC" - }, - "node_modules/responselike": { - "version": "2.0.0", "license": "MIT", "dependencies": { - "lowercase-keys": "^2.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/retry": { - "version": "0.13.1", + "node_modules/spdx-license-ids": { + "version": "3.0.11", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } + "license": "CC0-1.0" }, - "node_modules/reusify": { - "version": "1.0.4", + "node_modules/spdy": { + "version": "4.0.2", "dev": true, "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", + "node_modules/spdy-transport": { + "version": "3.0.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", + "node_modules/split": { + "version": "1.0.1", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "through": "2" }, "engines": { "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ripemd160": { - "version": "2.0.2", + "node_modules/sprintf-js": { + "version": "1.0.3", + "license": "BSD-3-Clause" + }, + "node_modules/stack-trace": { + "version": "0.0.10", "license": "MIT", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "engines": { + "node": "*" } }, - "node_modules/rrweb-cssom": { - "version": "0.6.0", + "node_modules/statuses": { + "version": "1.5.0", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/stream-browserify": { + "version": "3.0.0", "license": "MIT", "dependencies": { - "queue-microtask": "^1.2.2" + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/stream-buffers": { + "version": "3.0.2", + "license": "Unlicense", + "engines": { + "node": ">= 0.10.0" + } }, - "node_modules/safe-stable-stringify": { - "version": "2.3.1", + "node_modules/stream-combiner": { + "version": "0.2.2", "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "duplexer": "~0.1.1", + "through": "~2.3.4" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", + "node_modules/stream-shift": { + "version": "1.0.1", + "dev": true, "license": "MIT" }, - "node_modules/sanitize-html": { - "version": "2.13.0", + "node_modules/streamx": { + "version": "2.16.1", + "dev": true, "license": "MIT", "dependencies": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, - "node_modules/sanitize-html/node_modules/is-plain-object": { - "version": "5.0.0", + "node_modules/string_decoder": { + "version": "1.3.0", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "safe-buffer": "~5.2.0" } }, - "node_modules/sass": { - "version": "1.69.5", - "dev": true, + "node_modules/string-width": { + "version": "4.2.3", "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=8" } }, - "node_modules/sass-loader": { - "version": "12.6.0", - "dev": true, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", "license": "MIT", "dependencies": { - "klona": "^2.0.4", - "neo-async": "^2.6.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", - "sass": "^1.3.0", - "sass-embedded": "*", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - } + "node": ">=8" } }, - "node_modules/sax": { - "version": "1.2.1", - "license": "ISC" + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/saxes": { - "version": "6.0.0", - "dev": true, - "license": "ISC", + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", "dependencies": { - "xmlchars": "^2.2.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=v12.22.7" + "node": ">=8" } }, - "node_modules/schema-utils": { - "version": "3.3.0", - "dev": true, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 10.13.0" + "node": ">=8" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/select-hose": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.1.1", - "dev": true, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", "license": "MIT", "dependencies": { - "node-forge": "^1" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/semver": { - "version": "7.6.2", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/strip-ansi": { + "version": "5.2.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/send": { - "version": "0.19.0", - "dev": true, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "dev": true, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { + "node_modules/strip-final-newline": { "version": "2.0.0", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/send/node_modules/depd": { - "version": "2.0.0", + "node_modules/strip-indent": { + "version": "3.0.0", "dev": true, "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", + "node_modules/strip-json-comments": { + "version": "3.1.1", "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.0.5", "license": "MIT" }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", + "node_modules/style-loader": { + "version": "3.3.3", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" } }, - "node_modules/serialize-javascript": { - "version": "6.0.0", + "node_modules/supports-color": { + "version": "7.2.0", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "randombytes": "^2.1.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/serve-index": { - "version": "1.9.1", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", "dev": true, "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", + "node_modules/svg-pathdata": { + "version": "6.0.3", "dev": true, "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=12.0.0" } }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", + "node_modules/svg2ttf": { + "version": "6.0.3", "dev": true, "license": "MIT", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "@xmldom/xmldom": "^0.7.2", + "argparse": "^2.0.1", + "cubic2quad": "^1.2.1", + "lodash": "^4.17.10", + "microbuffer": "^1.0.0", + "svgpath": "^2.1.5" }, - "engines": { - "node": ">= 0.6" + "bin": { + "svg2ttf": "svg2ttf.js" } }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-static": { - "version": "1.16.2", + "node_modules/svgicons2svgfont": { + "version": "10.0.6", "dev": true, "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "commander": "^7.2.0", + "geometry-interfaces": "^1.1.4", + "glob": "^7.1.6", + "neatequal": "^1.0.0", + "readable-stream": "^3.4.0", + "sax": "^1.2.4", + "svg-pathdata": "^6.0.0" + }, + "bin": { + "svgicons2svgfont": "bin/svgicons2svgfont.js" }, "engines": { - "node": ">= 0.8.0" + "node": ">=12.0.0" } }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", + "node_modules/svgicons2svgfont/node_modules/commander": { + "version": "7.2.0", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 10" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "license": "MIT", + "node_modules/svgicons2svgfont/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/setimmediate": { - "version": "1.0.5", + "node_modules/svgicons2svgfont/node_modules/sax": { + "version": "1.2.4", + "dev": true, + "license": "ISC" + }, + "node_modules/svgpath": { + "version": "2.5.0", "dev": true, "license": "MIT" }, - "node_modules/setprototypeof": { - "version": "1.2.0", + "node_modules/symbol-tree": { + "version": "3.2.4", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/sh-syntax": { - "version": "0.4.2", + "node_modules/synckit": { + "version": "0.8.8", "dev": true, "license": "MIT", "dependencies": { + "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": "^14.18.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/unts" } }, - "node_modules/sha.js": { - "version": "2.4.11", - "license": "(MIT AND BSD-3-Clause)", + "node_modules/tapable": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" } }, - "node_modules/shallow-clone": { - "version": "3.0.1", + "node_modules/tar-stream": { + "version": "2.2.0", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "kind-of": "^6.0.2" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/shebang-command": { - "version": "2.0.0", + "node_modules/tcp-port-used": { + "version": "1.0.2", "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" + "debug": "4.3.1", + "is2": "^2.0.6" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", + "node_modules/tcp-port-used/node_modules/debug": { + "version": "4.3.1", "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/shell-quote": { - "version": "1.8.1", + "node_modules/terser": { + "version": "5.31.6", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" } }, - "node_modules/side-channel": { - "version": "1.0.6", + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "license": "ISC" - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "esbuild": { + "optional": true }, - { - "type": "consulting", - "url": "https://feross.org/support" + "uglify-js": { + "optional": true } - ], - "license": "MIT", - "optional": true + } }, - "node_modules/simple-get": { - "version": "4.0.1", + "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.2", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, + "license": "BSD-3-Clause", "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "randombytes": "^2.1.0" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "dev": true, + "license": "MIT" }, - "node_modules/sinon": { - "version": "14.0.0", + "node_modules/test-exclude": { + "version": "6.0.0", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "dependencies": { - "@sinonjs/commons": "^1.8.3", - "@sinonjs/fake-timers": "^9.1.2", - "@sinonjs/samsam": "^6.1.1", - "diff": "^5.0.0", - "nise": "^5.1.1", - "supports-color": "^7.2.0" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" + "engines": { + "node": ">=8" } }, - "node_modules/sinon/node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "dependencies": { - "@sinonjs/commons": "^1.7.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/slash": { - "version": "3.0.0", + "node_modules/text-hex": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/sockjs": { - "version": "0.3.24", + "node_modules/thenify": { + "version": "3.3.1", "dev": true, "license": "MIT", "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" + "any-promise": "^1.0.0" } }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", + "node_modules/thenify-all": { + "version": "1.6.0", "dev": true, "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/source-map": { - "version": "0.6.1", - "license": "BSD-3-Clause", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, "engines": { - "node": ">=0.10.0" + "node": ">=0.8" } }, - "node_modules/source-map-js": { - "version": "1.0.2", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } + "node_modules/through": { + "version": "2.3.8", + "license": "MIT" }, - "node_modules/source-map-support": { - "version": "0.5.21", + "node_modules/through2": { + "version": "2.0.5", "dev": true, "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "node_modules/spdx-correct": { - "version": "3.1.1", + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", "dev": true, - "license": "CC-BY-3.0" + "license": "MIT" }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", "dev": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "safe-buffer": "~5.1.0" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.11", + "node_modules/thunky": { + "version": "1.1.0", "dev": true, - "license": "CC0-1.0" + "license": "MIT" }, - "node_modules/spdy": { - "version": "4.0.2", - "dev": true, + "node_modules/time-stamp": { + "version": "1.1.0", "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, - "node_modules/spdy-transport": { - "version": "3.0.0", + "node_modules/timers-ext": { + "version": "0.1.7", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" + "es5-ext": "~0.10.46", + "next-tick": "1" } }, - "node_modules/split": { - "version": "1.0.1", + "node_modules/tmp": { + "version": "0.2.1", + "dev": true, "license": "MIT", "dependencies": { - "through": "2" + "rimraf": "^3.0.0" }, "engines": { - "node": "*" + "node": ">=8.17.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "license": "BSD-3-Clause" - }, - "node_modules/stack-trace": { - "version": "0.0.10", + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, "engines": { - "node": "*" + "node": ">=8.0" } }, - "node_modules/statuses": { - "version": "1.5.0", + "node_modules/toidentifier": { + "version": "1.0.1", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.6" } }, - "node_modules/stream-browserify": { - "version": "3.0.0", - "license": "MIT", + "node_modules/tough-cookie": { + "version": "4.1.3", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" } }, - "node_modules/stream-buffers": { - "version": "3.0.2", - "license": "Unlicense", + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.10.0" + "node": ">= 4.0.0" } }, - "node_modules/stream-combiner": { - "version": "0.2.2", + "node_modules/tr46": { + "version": "5.0.0", "license": "MIT", "dependencies": { - "duplexer": "~0.1.1", - "through": "~2.3.4" + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" } }, - "node_modules/stream-shift": { - "version": "1.0.1", + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, - "license": "MIT" + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } }, - "node_modules/streamx": { - "version": "2.16.1", + "node_modules/trim-newlines": { + "version": "3.0.1", + "dev": true, "license": "MIT", - "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" + "engines": { + "node": ">=8" } }, - "node_modules/string_decoder": { + "node_modules/triple-beam": { "version": "1.3.0", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" } }, - "node_modules/string-width": { - "version": "4.2.3", + "node_modules/ts-morph": { + "version": "23.0.0", + "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "@ts-morph/common": "~0.24.0", + "code-block-writer": "^13.0.1" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "license": "MIT", + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" }, - "engines": { - "node": ">=8" + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=8" + "node": ">=0.3.1" } }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "node_modules/tslib": { + "version": "2.6.2", + "license": "0BSD" }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/tsscmp": { + "version": "1.0.6", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.6.x" } }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/ttf2eot": { + "version": "2.0.0", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "argparse": "^1.0.6", + "microbuffer": "^1.0.0" }, - "engines": { - "node": ">=8" + "bin": { + "ttf2eot": "ttf2eot.js" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.5", + "node_modules/ttf2eot/node_modules/argparse": { + "version": "1.0.10", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "sprintf-js": "~1.0.2" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.5", + "node_modules/ttf2woff": { + "version": "2.0.2", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "argparse": "^1.0.6", + "microbuffer": "^1.0.0", + "pako": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "ttf2woff": "ttf2woff.js" } }, - "node_modules/strip-ansi": { - "version": "5.2.0", + "node_modules/ttf2woff/node_modules/argparse": { + "version": "1.0.10", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + "sprintf-js": "~1.0.2" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", + "node_modules/tunnel": { + "version": "0.0.6", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", + "node_modules/tunnel-agent": { + "version": "0.6.0", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", + "node_modules/type": { + "version": "1.2.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } + "license": "ISC" }, - "node_modules/strip-indent": { - "version": "3.0.0", + "node_modules/type-check": { + "version": "0.4.0", "dev": true, "license": "MIT", "dependencies": { - "min-indent": "^1.0.0" + "prelude-ls": "^1.2.1" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", + "node_modules/type-detect": { + "version": "4.0.8", "dev": true, "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/strnum": { - "version": "1.0.5", - "license": "MIT" - }, - "node_modules/style-loader": { - "version": "3.3.3", + "node_modules/type-fest": { + "version": "0.8.1", "dev": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" + "node": ">=8" } }, - "node_modules/supports-color": { - "version": "7.2.0", + "node_modules/type-is": { + "version": "1.6.18", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", + "node_modules/typed-rest-client": { + "version": "1.8.11", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" } }, - "node_modules/svg-pathdata": { - "version": "6.0.3", + "node_modules/typescript": { + "version": "5.0.4", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, "engines": { - "node": ">=12.0.0" + "node": ">=12.20" } }, - "node_modules/svg2ttf": { - "version": "6.0.3", + "node_modules/uc.micro": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/umd-compat-loader": { + "version": "2.1.2", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@xmldom/xmldom": "^0.7.2", - "argparse": "^2.0.1", - "cubic2quad": "^1.2.1", - "lodash": "^4.17.10", - "microbuffer": "^1.0.0", - "svgpath": "^2.1.5" - }, - "bin": { - "svg2ttf": "svg2ttf.js" + "ast-types": "^0.9.2", + "loader-utils": "^1.0.3", + "recast": "^0.11.17" } }, - "node_modules/svgicons2svgfont": { - "version": "10.0.6", + "node_modules/umd-compat-loader/node_modules/json5": { + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "commander": "^7.2.0", - "geometry-interfaces": "^1.1.4", - "glob": "^7.1.6", - "neatequal": "^1.0.0", - "readable-stream": "^3.4.0", - "sax": "^1.2.4", - "svg-pathdata": "^6.0.0" + "minimist": "^1.2.0" }, "bin": { - "svgicons2svgfont": "bin/svgicons2svgfont.js" - }, - "engines": { - "node": ">=12.0.0" + "json5": "lib/cli.js" } }, - "node_modules/svgicons2svgfont/node_modules/commander": { - "version": "7.2.0", + "node_modules/umd-compat-loader/node_modules/loader-utils": { + "version": "1.4.2", "dev": true, "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, "engines": { - "node": ">= 10" + "node": ">=4.0.0" } }, - "node_modules/svgicons2svgfont/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", + "node_modules/unbox-primitive": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svgicons2svgfont/node_modules/sax": { - "version": "1.2.4", - "dev": true, - "license": "ISC" - }, - "node_modules/svgpath": { - "version": "2.5.0", + "node_modules/underscore": { + "version": "1.13.6", "dev": true, "license": "MIT" }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "dev": true, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/unescape-html": { + "version": "1.1.0", "license": "MIT" }, - "node_modules/synckit": { - "version": "0.8.8", + "node_modules/universalify": { + "version": "2.0.0", "dev": true, "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" + "node": ">= 10.0.0" } }, - "node_modules/tapable": { - "version": "2.2.1", + "node_modules/unpipe": { + "version": "1.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "node_modules/tar-fs": { - "version": "2.1.1", + "node_modules/update-browserslist-db": { + "version": "1.0.16", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "optional": true, "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/tar-stream": { - "version": "2.2.0", + "node_modules/uri-js": { + "version": "4.4.1", "dev": true, - "license": "MIT", - "optional": true, + "license": "BSD-2-Clause", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "punycode": "^2.1.0" } }, - "node_modules/tcp-port-used": { - "version": "1.0.2", + "node_modules/url": { + "version": "0.10.3", "license": "MIT", "dependencies": { - "debug": "4.3.1", - "is2": "^2.0.6" + "punycode": "1.3.2", + "querystring": "0.2.0" } }, - "node_modules/tcp-port-used/node_modules/debug": { - "version": "4.3.1", + "node_modules/url-join": { + "version": "4.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/url-parse": { + "version": "1.5.10", + "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" } }, - "node_modules/terser": { - "version": "5.31.6", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "license": "MIT" + }, + "node_modules/util": { + "version": "0.12.5", + "license": "MIT", "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.10", + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" - }, "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } + "node": ">= 0.4.0" } }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "node_modules/uuid": { + "version": "9.0.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", "dev": true, "license": "MIT" }, - "node_modules/test-exclude": { - "version": "6.0.0", + "node_modules/v8-to-istanbul": { + "version": "9.2.0", "dev": true, "license": "ISC", "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10.12.0" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", + "node_modules/validate-npm-package-license": { + "version": "3.0.4", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/varstream": { + "version": "0.3.2", + "dev": true, + "dependencies": { + "readable-stream": "^1.0.33" }, - "engines": { - "node": "*" + "bin": { + "json2varstream": "cli/json2varstream.js", + "varstream2json": "cli/varstream2json.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=0.10.*" } }, - "node_modules/text-hex": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/text-table": { - "version": "0.2.0", + "node_modules/varstream/node_modules/isarray": { + "version": "0.0.1", "dev": true, "license": "MIT" }, - "node_modules/thenify": { - "version": "3.3.1", + "node_modules/varstream/node_modules/readable-stream": { + "version": "1.1.14", "dev": true, "license": "MIT", "dependencies": { - "any-promise": "^1.0.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "node_modules/thenify-all": { - "version": "1.6.0", + "node_modules/varstream/node_modules/string_decoder": { + "version": "0.10.31", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", "dev": true, "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, "engines": { - "node": ">=0.8" + "node": ">= 0.8" } }, - "node_modules/through": { - "version": "2.3.8", - "license": "MIT" - }, - "node_modules/through2": { - "version": "2.0.5", - "dev": true, + "node_modules/vinyl": { + "version": "2.2.1", "license": "MIT", "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/through2/node_modules/readable-stream": { - "version": "2.3.8", - "dev": true, + "node_modules/vscode-json-languageservice": { + "version": "5.3.5", "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "@vscode/l10n": "^0.0.13", + "jsonc-parser": "^3.2.0", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.3", + "vscode-uri": "^3.0.7" } }, - "node_modules/through2/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, + "node_modules/vscode-json-languageservice/node_modules/vscode-uri": { + "version": "3.0.7", "license": "MIT" }, - "node_modules/through2/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, + "node_modules/vscode-jsonrpc": { + "version": "5.0.1", "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" + "engines": { + "node": ">=8.0.0 || >=10.0.0" } }, - "node_modules/thunky": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/time-stamp": { - "version": "1.1.0", + "node_modules/vscode-languageclient": { + "version": "6.1.4", "license": "MIT", + "dependencies": { + "semver": "^6.3.0", + "vscode-languageserver-protocol": "3.15.3" + }, "engines": { - "node": ">=0.10.0" + "vscode": "^1.41.0" } }, - "node_modules/timers-ext": { - "version": "0.1.7", - "dev": true, + "node_modules/vscode-languageclient/node_modules/semver": { + "version": "6.3.1", "license": "ISC", - "dependencies": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/tmp": { - "version": "0.2.1", - "dev": true, + "node_modules/vscode-languageserver": { + "version": "6.1.1", "license": "MIT", "dependencies": { - "rimraf": "^3.0.0" + "vscode-languageserver-protocol": "^3.15.3" }, - "engines": { - "node": ">=8.17.0" + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, + "node_modules/vscode-languageserver-protocol": { + "version": "3.15.3", "license": "MIT", "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" + "vscode-jsonrpc": "^5.0.1", + "vscode-languageserver-types": "3.15.1" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "dev": true, + "node_modules/vscode-languageserver-protocol/node_modules/vscode-languageserver-types": { + "version": "3.15.1", + "license": "MIT" + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.8", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.3", + "license": "MIT" + }, + "node_modules/vscode-nls": { + "version": "5.2.0", + "license": "MIT" + }, + "node_modules/vscode-nls-dev": { + "version": "4.0.4", "license": "MIT", - "engines": { - "node": ">=0.6" + "dependencies": { + "ansi-colors": "^4.1.1", + "clone": "^2.1.2", + "event-stream": "^3.3.4", + "fancy-log": "^1.3.3", + "glob": "^7.2.0", + "iconv-lite": "^0.6.3", + "is": "^3.3.0", + "source-map": "^0.6.1", + "typescript": "^4.5.4", + "vinyl": "^2.2.1", + "xml2js": "^0.5.0", + "yargs": "^17.3.0" + }, + "bin": { + "vscl": "lib/vscl.js" } }, - "node_modules/tough-cookie": { - "version": "4.1.3", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/vscode-nls-dev/node_modules/glob": { + "version": "7.2.3", + "license": "ISC", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=6" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "dev": true, + "node_modules/vscode-nls-dev/node_modules/iconv-lite": { + "version": "0.6.3", "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, "engines": { - "node": ">= 4.0.0" + "node": ">=0.10.0" } }, - "node_modules/tr46": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" + "node_modules/vscode-nls-dev/node_modules/typescript": { + "version": "4.9.5", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=18" + "node": ">=4.2.0" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "dev": true, + "node_modules/vscode-nls-dev/node_modules/xml2js": { + "version": "0.5.0", "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, "engines": { - "node": ">=8" + "node": ">=4.0.0" } }, - "node_modules/triple-beam": { - "version": "1.3.0", + "node_modules/vscode-uri": { + "version": "2.1.2", "license": "MIT" }, - "node_modules/ts-api-utils": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-morph": { - "version": "23.0.0", - "dev": true, + "node_modules/vue": { + "version": "3.3.4", "license": "MIT", "dependencies": { - "@ts-morph/common": "~0.24.0", - "code-block-writer": "^13.0.1" + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" + "node_modules/vue-loader": { + "version": "17.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "watchpack": "^2.4.0" }, "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "webpack": "^4.1.0 || ^5.0.0-0" }, "peerDependenciesMeta": { - "@swc/core": { + "@vue/compiler-sfc": { "optional": true }, - "@swc/wasm": { + "vue": { "optional": true } } }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", + "node_modules/vue-style-loader": { + "version": "4.1.3", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" + "license": "MIT", + "dependencies": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" } }, - "node_modules/tslib": { - "version": "2.6.2", - "license": "0BSD" - }, - "node_modules/tsscmp": { - "version": "1.0.6", + "node_modules/vue-style-loader/node_modules/hash-sum": { + "version": "1.0.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.x" - } + "license": "MIT" }, - "node_modules/ttf2eot": { - "version": "2.0.0", + "node_modules/vue-style-loader/node_modules/json5": { + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.6", - "microbuffer": "^1.0.0" + "minimist": "^1.2.0" }, "bin": { - "ttf2eot": "ttf2eot.js" + "json5": "lib/cli.js" } }, - "node_modules/ttf2eot/node_modules/argparse": { - "version": "1.0.10", + "node_modules/vue-style-loader/node_modules/loader-utils": { + "version": "1.4.2", "dev": true, "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" } }, - "node_modules/ttf2woff": { - "version": "2.0.2", + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.6", - "microbuffer": "^1.0.0", - "pako": "^1.0.0" + "xml-name-validator": "^5.0.0" }, - "bin": { - "ttf2woff": "ttf2woff.js" + "engines": { + "node": ">=18" } }, - "node_modules/ttf2woff/node_modules/argparse": { - "version": "1.0.10", + "node_modules/watchpack": { + "version": "2.4.2", "dev": true, "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/tunnel": { - "version": "0.0.6", + "node_modules/wawoff2": { + "version": "2.0.1", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "woff2_compress.js": "bin/woff2_compress.js", + "woff2_decompress.js": "bin/woff2_decompress.js" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", + "node_modules/wbuf": { + "version": "1.7.3", "dev": true, - "license": "Apache-2.0", - "optional": true, + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" + "minimalistic-assert": "^1.0.0" } }, - "node_modules/type": { - "version": "1.2.0", - "dev": true, - "license": "ISC" + "node_modules/web-toolkit": { + "resolved": "packages/core/src/web", + "link": true }, - "node_modules/type-check": { - "version": "0.4.0", + "node_modules/web-tree-sitter": { + "version": "0.20.8", + "license": "MIT" + }, + "node_modules/webfont": { + "version": "11.2.26", "dev": true, "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1" + "cosmiconfig": "^5.2.0", + "deepmerge": "^4.2.2", + "globby": "^11.0.0", + "meow": "^9.0.0", + "nunjucks": "^3.2.3", + "p-limit": "^3.1.0", + "parse-json": "^5.2.0", + "resolve-from": "^5.0.0", + "svg2ttf": "^6.0.2", + "svgicons2svgfont": "^10.0.3", + "ttf2eot": "^2.0.0", + "ttf2woff": "^2.0.2", + "wawoff2": "^2.0.0", + "xml2js": "^0.4.23" + }, + "bin": { + "webfont": "dist/cli.js" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", + "node_modules/webfont/node_modules/resolve-from": { + "version": "5.0.0", "dev": true, "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=8" } }, - "node_modules/type-is": { - "version": "1.6.18", + "node_modules/webfont/node_modules/xml2js": { + "version": "0.5.0", "dev": true, "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=4.0.0" } }, - "node_modules/typed-rest-client": { - "version": "1.8.11", + "node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "dev": true, - "license": "MIT", "dependencies": { - "qs": "^6.9.1", - "tunnel": "0.0.6", - "underscore": "^1.12.1" + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } } }, - "node_modules/typescript": { - "version": "5.0.4", + "node_modules/webpack-cli": { + "version": "5.1.4", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=12.20" + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } } }, - "node_modules/uc.micro": { - "version": "1.0.6", - "license": "MIT" - }, - "node_modules/umd-compat-loader": { - "version": "2.1.2", + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "ast-types": "^0.9.2", - "loader-utils": "^1.0.3", - "recast": "^0.11.17" + "license": "MIT", + "engines": { + "node": ">=14" } }, - "node_modules/umd-compat-loader/node_modules/json5": { - "version": "1.0.2", + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.0" + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" }, - "bin": { - "json5": "lib/cli.js" + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/umd-compat-loader/node_modules/loader-utils": { - "version": "1.4.2", + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.11.0", "dev": true, "license": "MIT", "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=4.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "fast-deep-equal": "^3.1.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/underscore": { - "version": "1.13.6", + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", "dev": true, "license": "MIT" }, - "node_modules/unescape-html": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/universalify": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", "dev": true, "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/update-browserslist-db": { - "version": "1.0.16", + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" }, "bin": { - "update-browserslist-db": "cli.js" + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "browserslist": ">= 4.21.0" + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } } }, - "node_modules/uri-js": { - "version": "4.4.1", + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.8.2", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "punycode": "^2.1.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/url": { - "version": "0.10.3", + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "dev": true, "license": "MIT", "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/url-join": { - "version": "4.0.1", + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", "dev": true, "license": "MIT" }, - "node_modules/url-parse": { - "version": "1.5.10", + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", "dev": true, "license": "MIT", "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "license": "MIT" - }, - "node_modules/util": { - "version": "0.12.5", + "node_modules/webpack-merge": { + "version": "5.10.0", + "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", + "node_modules/webpack-sources": { + "version": "2.3.1", "dev": true, "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, "engines": { - "node": ">= 0.4.0" + "node": ">=10.13.0" } }, - "node_modules/uuid": { - "version": "9.0.1", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/webpack/node_modules/events": { + "version": "3.3.0", + "dev": true, "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">=0.8.x" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", + "node_modules/webpack/node_modules/webpack-sources": { + "version": "3.2.3", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } }, - "node_modules/v8-to-istanbul": { - "version": "9.2.0", + "node_modules/websocket-driver": { + "version": "0.7.4", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" }, "engines": { - "node": ">=10.12.0" + "node": ">=0.8.0" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", + "node_modules/websocket-extensions": { + "version": "0.1.4", "dev": true, "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "engines": { + "node": ">=0.8.0" } }, - "node_modules/varstream": { - "version": "0.3.2", + "node_modules/whatwg-encoding": { + "version": "3.1.1", "dev": true, + "license": "MIT", "dependencies": { - "readable-stream": "^1.0.33" - }, - "bin": { - "json2varstream": "cli/json2varstream.js", - "varstream2json": "cli/varstream2json.js" + "iconv-lite": "0.6.3" }, "engines": { - "node": ">=0.10.*" + "node": ">=18" } }, - "node_modules/varstream/node_modules/isarray": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/varstream/node_modules/readable-stream": { - "version": "1.1.14", + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", "dev": true, "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/varstream/node_modules/string_decoder": { - "version": "0.10.31", - "dev": true, - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", + "node_modules/whatwg-mimetype": { + "version": "4.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/vinyl": { - "version": "2.2.1", + "node_modules/whatwg-url": { + "version": "14.0.0", "license": "MIT", "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=18" } }, - "node_modules/vscode-json-languageservice": { - "version": "5.3.5", - "license": "MIT", + "node_modules/which": { + "version": "2.0.2", + "license": "ISC", "dependencies": { - "@vscode/l10n": "^0.0.13", - "jsonc-parser": "^3.2.0", - "vscode-languageserver-textdocument": "^1.0.8", - "vscode-languageserver-types": "^3.17.3", - "vscode-uri": "^3.0.7" + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/vscode-json-languageservice/node_modules/vscode-uri": { - "version": "3.0.7", - "license": "MIT" - }, - "node_modules/vscode-jsonrpc": { - "version": "5.0.1", + "node_modules/which-boxed-primitive": { + "version": "1.0.2", "license": "MIT", - "engines": { - "node": ">=8.0.0 || >=10.0.0" + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vscode-languageclient": { - "version": "6.1.4", + "node_modules/which-typed-array": { + "version": "1.1.8", "license": "MIT", "dependencies": { - "semver": "^6.3.0", - "vscode-languageserver-protocol": "3.15.3" + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-abstract": "^1.20.0", + "for-each": "^0.3.3", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.9" }, "engines": { - "vscode": "^1.41.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vscode-languageclient/node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } + "node_modules/wildcard": { + "version": "2.0.0", + "dev": true, + "license": "MIT" }, - "node_modules/vscode-languageserver": { - "version": "6.1.1", + "node_modules/winston": { + "version": "3.11.0", "license": "MIT", "dependencies": { - "vscode-languageserver-protocol": "^3.15.3" + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" + "engines": { + "node": ">= 12.0.0" } }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.15.3", + "node_modules/winston-transport": { + "version": "4.6.0", "license": "MIT", "dependencies": { - "vscode-jsonrpc": "^5.0.1", - "vscode-languageserver-types": "3.15.1" + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" } }, - "node_modules/vscode-languageserver-protocol/node_modules/vscode-languageserver-types": { - "version": "3.15.1", - "license": "MIT" - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.8", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.3", - "license": "MIT" + "node_modules/winston/node_modules/@colors/colors": { + "version": "1.6.0", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } }, - "node_modules/vscode-nls": { - "version": "5.2.0", - "license": "MIT" + "node_modules/workerpool": { + "version": "6.2.1", + "dev": true, + "license": "Apache-2.0" }, - "node_modules/vscode-nls-dev": { - "version": "4.0.4", + "node_modules/wrap-ansi": { + "version": "7.0.0", "license": "MIT", "dependencies": { - "ansi-colors": "^4.1.1", - "clone": "^2.1.2", - "event-stream": "^3.3.4", - "fancy-log": "^1.3.3", - "glob": "^7.2.0", - "iconv-lite": "^0.6.3", - "is": "^3.3.0", - "source-map": "^0.6.1", - "typescript": "^4.5.4", - "vinyl": "^2.2.1", - "xml2js": "^0.5.0", - "yargs": "^17.3.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, - "bin": { - "vscl": "lib/vscl.js" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/vscode-nls-dev/node_modules/glob": { - "version": "7.2.3", - "license": "ISC", + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "*" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/vscode-nls-dev/node_modules/iconv-lite": { - "version": "0.6.3", + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/vscode-nls-dev/node_modules/typescript": { - "version": "4.9.5", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=4.2.0" + "node": ">=8" } }, - "node_modules/vscode-nls-dev/node_modules/xml2js": { - "version": "0.5.0", + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, "engines": { - "node": ">=4.0.0" + "node": ">=8" } }, - "node_modules/vscode-uri": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/vue": { - "version": "3.3.4", + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.3.4", - "@vue/compiler-sfc": "3.3.4", - "@vue/runtime-dom": "3.3.4", - "@vue/server-renderer": "3.3.4", - "@vue/shared": "3.3.4" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/vue-loader": { - "version": "17.2.2", + "node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.17.1", "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "watchpack": "^2.4.0" + "engines": { + "node": ">=10.0.0" }, "peerDependencies": { - "webpack": "^4.1.0 || ^5.0.0-0" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { - "@vue/compiler-sfc": { + "bufferutil": { "optional": true }, - "vue": { + "utf-8-validate": { "optional": true } } }, - "node_modules/vue-style-loader": { - "version": "4.1.3", - "dev": true, - "license": "MIT", - "dependencies": { - "hash-sum": "^1.0.2", - "loader-utils": "^1.0.2" - } - }, - "node_modules/vue-style-loader/node_modules/hash-sum": { - "version": "1.0.2", + "node_modules/xml": { + "version": "1.0.1", "dev": true, "license": "MIT" }, - "node_modules/vue-style-loader/node_modules/json5": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/vue-style-loader/node_modules/loader-utils": { - "version": "1.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/w3c-xmlserializer": { + "node_modules/xml-name-validator": { "version": "5.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, + "license": "Apache-2.0", "engines": { "node": ">=18" } }, - "node_modules/watchpack": { - "version": "2.4.2", - "dev": true, + "node_modules/xml2js": { + "version": "0.6.1", "license": "MIT", "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=4.0.0" } }, - "node_modules/wawoff2": { - "version": "2.0.1", - "dev": true, + "node_modules/xmlbuilder": { + "version": "11.0.1", "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "woff2_compress.js": "bin/woff2_compress.js", - "woff2_decompress.js": "bin/woff2_decompress.js" + "engines": { + "node": ">=4.0" } }, - "node_modules/wbuf": { - "version": "1.7.3", + "node_modules/xmlchars": { + "version": "2.2.0", "dev": true, - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/web-toolkit": { - "resolved": "packages/core/src/web", - "link": true - }, - "node_modules/web-tree-sitter": { - "version": "0.20.8", "license": "MIT" }, - "node_modules/webfont": { - "version": "11.2.26", + "node_modules/xtend": { + "version": "4.0.2", "dev": true, "license": "MIT", - "dependencies": { - "cosmiconfig": "^5.2.0", - "deepmerge": "^4.2.2", - "globby": "^11.0.0", - "meow": "^9.0.0", - "nunjucks": "^3.2.3", - "p-limit": "^3.1.0", - "parse-json": "^5.2.0", - "resolve-from": "^5.0.0", - "svg2ttf": "^6.0.2", - "svgicons2svgfont": "^10.0.3", - "ttf2eot": "^2.0.0", - "ttf2woff": "^2.0.2", - "wawoff2": "^2.0.0", - "xml2js": "^0.4.23" - }, - "bin": { - "webfont": "dist/cli.js" - }, "engines": { - "node": ">= 12.0.0" + "node": ">=0.4" } }, - "node_modules/webfont/node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", + "node_modules/y18n": { + "version": "5.0.8", + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/webfont/node_modules/xml2js": { - "version": "0.5.0", + "node_modules/yallist": { + "version": "4.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } + "license": "ISC" }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "license": "BSD-2-Clause", + "node_modules/yaml": { + "version": "1.10.2", + "license": "ISC", "engines": { - "node": ">=12" + "node": ">= 6" } }, - "node_modules/webpack": { - "version": "5.94.0", - "dev": true, - "license": "MIT", + "node_modules/yaml-ast-parser-custom-tags": { + "version": "0.0.43", + "license": "Apache-2.0" + }, + "node_modules/yaml-cfn": { + "version": "0.3.2", + "license": "Apache-2.0", "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "js-yaml": "^4.0.0" }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } + "bin": { + "yaml-cfn": "cli.js" } }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "dev": true, + "node_modules/yaml-language-server": { + "version": "0.15.0", "license": "MIT", "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "js-yaml": "^3.13.1", + "jsonc-parser": "^2.2.1", + "request-light": "^0.2.4", + "vscode-json-languageservice": "^3.10.0", + "vscode-languageserver": "^5.2.1", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-nls": "^4.1.2", + "vscode-uri": "^2.1.1", + "yaml-language-server-parser": "0.1.2" }, "bin": { - "webpack-cli": "bin/cli.js" + "yaml-language-server": "bin/yaml-language-server" }, "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" + "node": "*" }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } + "optionalDependencies": { + "prettier": "2.0.5" } }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "dev": true, + "node_modules/yaml-language-server-parser": { + "version": "0.1.2", + "license": "Apache-2.0" + }, + "node_modules/yaml-language-server/node_modules/argparse": { + "version": "1.0.10", "license": "MIT", - "engines": { - "node": ">=14" + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "dev": true, + "node_modules/yaml-language-server/node_modules/js-yaml": { + "version": "3.14.1", "license": "MIT", "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.11.0", - "dev": true, + "node_modules/yaml-language-server/node_modules/jsonc-parser": { + "version": "2.3.1", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/prettier": { + "version": "2.0.5", "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "optional": true, + "bin": { + "prettier": "bin-prettier.js" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=10.13.0" } }, - "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { - "version": "5.1.0", - "dev": true, + "node_modules/yaml-language-server/node_modules/vscode-json-languageservice": { + "version": "3.11.0", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" + "jsonc-parser": "^3.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "3.16.0-next.2", + "vscode-nls": "^5.0.0", + "vscode-uri": "^2.1.2" } }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "dev": true, + "node_modules/yaml-language-server/node_modules/vscode-json-languageservice/node_modules/jsonc-parser": { + "version": "3.2.0", "license": "MIT" }, - "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "node_modules/yaml-language-server/node_modules/vscode-json-languageservice/node_modules/vscode-languageserver-types": { + "version": "3.16.0-next.2", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/vscode-json-languageservice/node_modules/vscode-nls": { + "version": "5.2.0", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/vscode-jsonrpc": { "version": "4.0.0", - "dev": true, "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" - }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=8.0.0 || >=10.0.0" } }, - "node_modules/webpack-dev-server": { - "version": "4.15.1", - "dev": true, + "node_modules/yaml-language-server/node_modules/vscode-languageserver": { + "version": "5.2.1", "license": "MIT", "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" + "vscode-languageserver-protocol": "3.14.1", + "vscode-uri": "^1.0.6" }, "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/yaml-language-server/node_modules/vscode-languageserver-protocol": { + "version": "3.14.1", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "^4.0.0", + "vscode-languageserver-types": "3.14.0" + } + }, + "node_modules/yaml-language-server/node_modules/vscode-languageserver-protocol/node_modules/vscode-languageserver-types": { + "version": "3.14.0", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/vscode-languageserver/node_modules/vscode-uri": { + "version": "1.0.8", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/vscode-nls": { + "version": "4.1.2", + "license": "MIT" + }, + "node_modules/yargs": { + "version": "17.7.2", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } + "node": ">=12" } }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.8.2", + "node_modules/yargs-parser": { + "version": "20.2.4", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=10" } }, - "node_modules/webpack-dev-server/node_modules/ajv-keywords": { - "version": "5.1.0", - "dev": true, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/cliui": { + "version": "8.0.1", + "license": "ISC", "dependencies": { - "fast-deep-equal": "^3.1.3" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, - "peerDependencies": { - "ajv": "^8.8.2" + "engines": { + "node": ">=12" } }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.0.0", - "dev": true, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=8" } }, - "node_modules/webpack-merge": { - "version": "5.10.0", + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", "dev": true, "license": "MIT", "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } }, - "node_modules/webpack-sources": { - "version": "2.3.1", + "node_modules/yazl": { + "version": "2.5.1", "dev": true, "license": "MIT", "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10.13.0" + "buffer-crc32": "~0.2.3" } }, - "node_modules/webpack/node_modules/events": { - "version": "3.3.0", + "node_modules/ylru": { + "version": "1.4.0", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.x" + "node": ">= 4.0.0" } }, - "node_modules/webpack/node_modules/webpack-sources": { - "version": "3.2.3", + "node_modules/yn": { + "version": "3.1.1", "dev": true, "license": "MIT", "engines": { - "node": ">=10.13.0" + "node": ">=6" } }, - "node_modules/websocket-driver": { - "version": "0.7.4", + "node_modules/yocto-queue": { + "version": "0.1.0", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "dev": true, + "packages/amazonq": { + "name": "amazon-q-vscode", + "version": "1.42.0-SNAPSHOT", "license": "Apache-2.0", + "dependencies": { + "aws-core-vscode": "file:../core/" + }, "engines": { - "node": ">=0.8.0" + "npm": "^10.1.0", + "vscode": "^1.83.0" } }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "dev": true, - "license": "MIT", + "packages/core": { + "name": "aws-core-vscode", + "version": "1.0.0", + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "iconv-lite": "0.6.3" + "@amzn/amazon-q-developer-streaming-client": "file:../../src.gen/@amzn/amazon-q-developer-streaming-client", + "@amzn/codewhisperer-streaming": "file:../../src.gen/@amzn/codewhisperer-streaming", + "@aws-sdk/client-cloudformation": "^3.667.0", + "@aws-sdk/client-cloudwatch-logs": "^3.666.0", + "@aws-sdk/client-cognito-identity": "^3.637.0", + "@aws-sdk/client-lambda": "^3.637.0", + "@aws-sdk/client-sso": "^3.342.0", + "@aws-sdk/client-sso-oidc": "^3.574.0", + "@aws-sdk/credential-provider-ini": "3.46.0", + "@aws-sdk/credential-provider-process": "3.37.0", + "@aws-sdk/credential-provider-sso": "^3.345.0", + "@aws-sdk/property-provider": "3.46.0", + "@aws-sdk/smithy-client": "^3.46.0", + "@aws-sdk/util-arn-parser": "^3.46.0", + "@aws/mynah-ui": "^4.21.3", + "@gerhobbelt/gitignore-parser": "^0.2.0-9", + "@iarna/toml": "^2.2.5", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/protocol-http": "^3.3.0", + "@smithy/service-error-classification": "^2.1.5", + "@smithy/shared-ini-file-loader": "^2.2.8", + "@smithy/util-retry": "^2.2.0", + "@vscode/debugprotocol": "^1.57.0", + "@zip.js/zip.js": "^2.7.41", + "adm-zip": "^0.5.10", + "amazon-states-language-service": "^1.13.0", + "async-lock": "^1.4.0", + "aws-sdk": "^2.1384.0", + "aws-ssm-document-language-service": "^1.0.0", + "bytes": "^3.1.2", + "cross-fetch": "^4.0.0", + "cross-spawn": "^7.0.5", + "diff": "^5.1.0", + "fast-json-patch": "^3.1.1", + "glob": "^10.3.10", + "got": "^11.8.5", + "highlight.js": "^11.9.0", + "i18n-ts": "^1.0.5", + "immutable": "^4.3.0", + "jose": "5.4.1", + "js-yaml": "^4.1.0", + "jsonc-parser": "^3.2.0", + "lodash": "^4.17.21", + "markdown-it": "^13.0.2", + "mime-types": "^2.1.32", + "node-fetch": "^2.7.0", + "portfinder": "^1.0.32", + "semver": "^7.5.4", + "stream-buffers": "^3.0.2", + "strip-ansi": "^5.2.0", + "tcp-port-used": "^1.0.1", + "vscode-languageclient": "^6.1.4", + "vscode-languageserver": "^6.1.1", + "vscode-languageserver-protocol": "^3.15.3", + "vscode-languageserver-textdocument": "^1.0.8", + "vue": "^3.3.4", + "web-tree-sitter": "^0.20.8", + "whatwg-url": "^14.0.0", + "winston": "^3.11.0", + "winston-transport": "^4.6.0", + "xml2js": "^0.6.1", + "yaml-cfn": "^0.3.2" + }, + "devDependencies": { + "@aws-sdk/types": "^3.13.1", + "@aws/fully-qualified-names": "^2.1.4", + "@cspotcode/source-map-support": "^0.8.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/adm-zip": "^0.4.34", + "@types/async-lock": "^1.4.0", + "@types/bytes": "^3.1.0", + "@types/circular-dependency-plugin": "^5.0.8", + "@types/cross-spawn": "^6.0.6", + "@types/diff": "^5.0.7", + "@types/glob": "^8.1.0", + "@types/js-yaml": "^4.0.5", + "@types/jsdom": "^21.1.6", + "@types/lodash": "^4.14.180", + "@types/markdown-it": "^13.0.2", + "@types/mime-types": "^2.1.4", + "@types/mocha": "^10.0.6", + "@types/node": "^16.18.95", + "@types/node-fetch": "^2.6.8", + "@types/prismjs": "^1.26.0", + "@types/proper-lockfile": "^4.1.4", + "@types/readline-sync": "^1.4.8", + "@types/semver": "^7.5.0", + "@types/sinon": "^10.0.5", + "@types/sinonjs__fake-timers": "^8.1.2", + "@types/stream-buffers": "^3.0.7", + "@types/tcp-port-used": "^1.0.1", + "@types/uuid": "^9.0.1", + "@types/whatwg-url": "^11.0.4", + "@types/xml2js": "^0.4.11", + "@vue/compiler-sfc": "^3.3.2", + "c8": "^9.0.0", + "circular-dependency-plugin": "^5.2.2", + "css-loader": "^6.10.0", + "esbuild-loader": "2.20.0", + "file-loader": "^6.2.0", + "jsdom": "^23.0.1", + "json-schema-to-typescript": "^13.1.1", + "marked": "^13.0.1", + "mocha": "^10.1.0", + "mocha-junit-reporter": "^2.2.1", + "mocha-multi-reporters": "^1.5.1", + "readline-sync": "^1.4.9", + "sass": "^1.49.8", + "sass-loader": "^16.0.2", + "sinon": "^14.0.0", + "style-loader": "^3.3.1", + "ts-node": "^10.9.1", + "typescript": "^5.0.4", + "umd-compat-loader": "^2.1.2", + "vue-loader": "^17.2.2", + "vue-style-loader": "^4.1.3", + "webfont": "^11.2.26" }, "engines": { - "node": ">=18" + "npm": "^10.1.0", + "vscode": "^1.83.0" } }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", + "packages/core/node_modules/@types/node": { + "version": "16.18.95", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "packages/core/src/web": { + "name": "web-toolkit", + "license": "Apache-2.0", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "crypto-browserify": "^3.12.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "stream-browserify": "^3.0.0" }, - "engines": { - "node": ">=0.10.0" + "devDependencies": { + "assert": "^2.1.0" } }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "dev": true, - "license": "MIT", + "packages/toolkit": { + "name": "aws-toolkit-vscode", + "version": "3.41.0-SNAPSHOT", + "license": "Apache-2.0", + "dependencies": { + "aws-core-vscode": "file:../core/" + }, + "devDependencies": {}, "engines": { - "node": ">=18" + "npm": "^10.1.0", + "vscode": "^1.83.0" } }, - "node_modules/whatwg-url": { - "version": "14.0.0", - "license": "MIT", - "dependencies": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" + "plugins/eslint-plugin-aws-toolkits": { + "version": "1.0.0", + "license": "Apache-2.0", + "devDependencies": { + "mocha": "^10.1.0" }, "engines": { - "node": ">=18" + "npm": "^10.1.0" } }, - "node_modules/which": { - "version": "2.0.2", - "license": "ISC", + "src.gen/@amzn/amazon-q-developer-streaming-client": { + "version": "1.0.0", + "hasInstallScript": true, "dependencies": { - "isexe": "^2.0.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.658.1", + "@aws-sdk/client-sts": "3.658.1", + "@aws-sdk/core": "3.658.1", + "@aws-sdk/credential-provider-node": "3.658.1", + "@aws-sdk/middleware-host-header": "3.654.0", + "@aws-sdk/middleware-logger": "3.654.0", + "@aws-sdk/middleware-recursion-detection": "3.654.0", + "@aws-sdk/middleware-user-agent": "3.654.0", + "@aws-sdk/region-config-resolver": "3.654.0", + "@aws-sdk/types": "3.654.0", + "@aws-sdk/util-user-agent-browser": "3.654.0", + "@aws-sdk/util-user-agent-node": "3.654.0", + "@smithy/config-resolver": "^3.0.8", + "@smithy/core": "^2.4.6", + "@smithy/eventstream-serde-browser": "^3.0.9", + "@smithy/eventstream-serde-config-resolver": "^3.0.6", + "@smithy/eventstream-serde-node": "^3.0.8", + "@smithy/fetch-http-handler": "^3.2.8", + "@smithy/hash-node": "^3.0.6", + "@smithy/invalid-dependency": "^3.0.6", + "@smithy/middleware-content-length": "^3.0.8", + "@smithy/middleware-retry": "^3.0.21", + "@smithy/middleware-serde": "^3.0.6", + "@smithy/middleware-stack": "^3.0.6", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/node-http-handler": "^3.2.3", + "@smithy/protocol-http": "^4.1.3", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/url-parser": "^3.0.6", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.21", + "@smithy/util-defaults-mode-node": "^3.0.21", + "@smithy/util-middleware": "^3.0.6", + "@smithy/util-retry": "^3.0.6", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, - "bin": { - "node-which": "bin/node-which" + "devDependencies": { + "@tsconfig/node16": "16.1.3", + "@types/node": "^16.18.96", + "@types/uuid": "^9.0.4", + "concurrently": "7.0.0", + "downlevel-dts": "0.10.1", + "rimraf": "^3.0.0", + "typescript": "~4.9.5" }, "engines": { - "node": ">= 8" + "node": ">=16.0.0" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/client-sso": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.658.1.tgz", + "integrity": "sha512-lOuaBtqPTYGn6xpXlQF4LsNDsQ8Ij2kOdnk+i69Kp6yS76TYvtUuukyLL5kx8zE1c8WbYtxj9y8VNw9/6uKl7Q==", + "license": "Apache-2.0", "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.658.1", + "@aws-sdk/middleware-host-header": "3.654.0", + "@aws-sdk/middleware-logger": "3.654.0", + "@aws-sdk/middleware-recursion-detection": "3.654.0", + "@aws-sdk/middleware-user-agent": "3.654.0", + "@aws-sdk/region-config-resolver": "3.654.0", + "@aws-sdk/types": "3.654.0", + "@aws-sdk/util-endpoints": "3.654.0", + "@aws-sdk/util-user-agent-browser": "3.654.0", + "@aws-sdk/util-user-agent-node": "3.654.0", + "@smithy/config-resolver": "^3.0.8", + "@smithy/core": "^2.4.6", + "@smithy/fetch-http-handler": "^3.2.8", + "@smithy/hash-node": "^3.0.6", + "@smithy/invalid-dependency": "^3.0.6", + "@smithy/middleware-content-length": "^3.0.8", + "@smithy/middleware-endpoint": "^3.1.3", + "@smithy/middleware-retry": "^3.0.21", + "@smithy/middleware-serde": "^3.0.6", + "@smithy/middleware-stack": "^3.0.6", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/node-http-handler": "^3.2.3", + "@smithy/protocol-http": "^4.1.3", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/url-parser": "^3.0.6", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.21", + "@smithy/util-defaults-mode-node": "^3.0.21", + "@smithy/util-endpoints": "^2.1.2", + "@smithy/util-middleware": "^3.0.6", + "@smithy/util-retry": "^3.0.6", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/which-typed-array": { - "version": "1.1.8", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.658.1.tgz", + "integrity": "sha512-RGcZAI3qEA05JszPKwa0cAyp8rnS1nUvs0Sqw4hqLNQ1kD7b7V6CPjRXe7EFQqCOMvM4kGqx0+cEEVTOmBsFLw==", + "license": "Apache-2.0", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", - "for-each": "^0.3.3", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.9" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.658.1", + "@aws-sdk/credential-provider-node": "3.658.1", + "@aws-sdk/middleware-host-header": "3.654.0", + "@aws-sdk/middleware-logger": "3.654.0", + "@aws-sdk/middleware-recursion-detection": "3.654.0", + "@aws-sdk/middleware-user-agent": "3.654.0", + "@aws-sdk/region-config-resolver": "3.654.0", + "@aws-sdk/types": "3.654.0", + "@aws-sdk/util-endpoints": "3.654.0", + "@aws-sdk/util-user-agent-browser": "3.654.0", + "@aws-sdk/util-user-agent-node": "3.654.0", + "@smithy/config-resolver": "^3.0.8", + "@smithy/core": "^2.4.6", + "@smithy/fetch-http-handler": "^3.2.8", + "@smithy/hash-node": "^3.0.6", + "@smithy/invalid-dependency": "^3.0.6", + "@smithy/middleware-content-length": "^3.0.8", + "@smithy/middleware-endpoint": "^3.1.3", + "@smithy/middleware-retry": "^3.0.21", + "@smithy/middleware-serde": "^3.0.6", + "@smithy/middleware-stack": "^3.0.6", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/node-http-handler": "^3.2.3", + "@smithy/protocol-http": "^4.1.3", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/url-parser": "^3.0.6", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.21", + "@smithy/util-defaults-mode-node": "^3.0.21", + "@smithy/util-endpoints": "^2.1.2", + "@smithy/util-middleware": "^3.0.6", + "@smithy/util-retry": "^3.0.6", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 0.4" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.658.1" } }, - "node_modules/wildcard": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/winston": { - "version": "3.11.0", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/client-sts": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.658.1.tgz", + "integrity": "sha512-yw9hc5blTnbT1V6mR7Cx9HGc9KQpcLQ1QXj8rntiJi6tIYu3aFNVEyy81JHL7NsuBSeQulJTvHO3y6r3O0sfRg==", + "license": "Apache-2.0", "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.4.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.658.1", + "@aws-sdk/core": "3.658.1", + "@aws-sdk/credential-provider-node": "3.658.1", + "@aws-sdk/middleware-host-header": "3.654.0", + "@aws-sdk/middleware-logger": "3.654.0", + "@aws-sdk/middleware-recursion-detection": "3.654.0", + "@aws-sdk/middleware-user-agent": "3.654.0", + "@aws-sdk/region-config-resolver": "3.654.0", + "@aws-sdk/types": "3.654.0", + "@aws-sdk/util-endpoints": "3.654.0", + "@aws-sdk/util-user-agent-browser": "3.654.0", + "@aws-sdk/util-user-agent-node": "3.654.0", + "@smithy/config-resolver": "^3.0.8", + "@smithy/core": "^2.4.6", + "@smithy/fetch-http-handler": "^3.2.8", + "@smithy/hash-node": "^3.0.6", + "@smithy/invalid-dependency": "^3.0.6", + "@smithy/middleware-content-length": "^3.0.8", + "@smithy/middleware-endpoint": "^3.1.3", + "@smithy/middleware-retry": "^3.0.21", + "@smithy/middleware-serde": "^3.0.6", + "@smithy/middleware-stack": "^3.0.6", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/node-http-handler": "^3.2.3", + "@smithy/protocol-http": "^4.1.3", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/url-parser": "^3.0.6", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.21", + "@smithy/util-defaults-mode-node": "^3.0.21", + "@smithy/util-endpoints": "^2.1.2", + "@smithy/util-middleware": "^3.0.6", + "@smithy/util-retry": "^3.0.6", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/winston-transport": { - "version": "4.6.0", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/core": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.658.1.tgz", + "integrity": "sha512-vJVMoMcSKXK2gBRSu9Ywwv6wQ7tXH8VL1fqB1uVxgCqBZ3IHfqNn4zvpMPWrwgO2/3wv7XFyikGQ5ypPTCw4jA==", + "license": "Apache-2.0", "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" + "@smithy/core": "^2.4.6", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/property-provider": "^3.1.6", + "@smithy/protocol-http": "^4.1.3", + "@smithy/signature-v4": "^4.1.4", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/util-middleware": "^3.0.6", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">=16.0.0" } }, - "node_modules/winston/node_modules/@colors/colors": { - "version": "1.6.0", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.654.0.tgz", + "integrity": "sha512-kogsx3Ql81JouHS7DkheCDU9MYAvK0AokxjcshDveGmf7BbgbWCA8Fnb9wjQyNDaOXNvkZu8Z8rgkX91z324/w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/property-provider": "^3.1.6", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.1.90" + "node": ">=16.0.0" } }, - "node_modules/workerpool": { - "version": "6.2.1", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.658.1.tgz", + "integrity": "sha512-4ubkJjEVCZflxkZnV1JDQv8P2pburxk1LrEp55telfJRzXrnowzBKwuV2ED0QMNC448g2B3VCaffS+Ct7c4IWQ==", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "@aws-sdk/types": "3.654.0", + "@smithy/fetch-http-handler": "^3.2.8", + "@smithy/node-http-handler": "^3.2.3", + "@smithy/property-provider": "^3.1.6", + "@smithy/protocol-http": "^4.1.3", + "@smithy/smithy-client": "^3.3.5", + "@smithy/types": "^3.4.2", + "@smithy/util-stream": "^3.1.8", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=16.0.0" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.658.1.tgz", + "integrity": "sha512-2uwOamQg5ppwfegwen1ddPu5HM3/IBSnaGlaKLFhltkdtZ0jiqTZWUtX2V+4Q+buLnT0hQvLS/frQ+7QUam+0Q==", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "@aws-sdk/credential-provider-env": "3.654.0", + "@aws-sdk/credential-provider-http": "3.658.1", + "@aws-sdk/credential-provider-process": "3.654.0", + "@aws-sdk/credential-provider-sso": "3.658.1", + "@aws-sdk/credential-provider-web-identity": "3.654.0", + "@aws-sdk/types": "3.654.0", + "@smithy/credential-provider-imds": "^3.2.3", + "@smithy/property-provider": "^3.1.6", + "@smithy/shared-ini-file-loader": "^3.1.7", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.658.1" } }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.658.1.tgz", + "integrity": "sha512-XwxW6N+uPXPYAuyq+GfOEdfL/MZGAlCSfB5gEWtLBFmFbikhmEuqfWtI6CD60OwudCUOh6argd21BsJf8o1SJA==", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^5.0.1" + "@aws-sdk/credential-provider-env": "3.654.0", + "@aws-sdk/credential-provider-http": "3.658.1", + "@aws-sdk/credential-provider-ini": "3.658.1", + "@aws-sdk/credential-provider-process": "3.654.0", + "@aws-sdk/credential-provider-sso": "3.658.1", + "@aws-sdk/credential-provider-web-identity": "3.654.0", + "@aws-sdk/types": "3.654.0", + "@smithy/credential-provider-imds": "^3.2.3", + "@smithy/property-provider": "^3.1.6", + "@smithy/shared-ini-file-loader": "^3.1.7", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.654.0.tgz", + "integrity": "sha512-PmQoo8sZ9Q2Ow8OMzK++Z9lI7MsRUG7sNq3E72DVA215dhtTICTDQwGlXH2AAmIp7n+G9LLRds+4wo2ehG4mkg==", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^5.0.1" + "@aws-sdk/types": "3.654.0", + "@smithy/property-provider": "^3.1.6", + "@smithy/shared-ini-file-loader": "^3.1.7", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.17.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=16.0.0" } }, - "node_modules/xml": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "dev": true, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.658.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.658.1.tgz", + "integrity": "sha512-YOagVEsZEk9DmgJEBg+4MBXrPcw/tYas0VQ5OVBqC5XHNbi2OBGJqgmjVPesuu393E7W0VQxtJFDS00O1ewQgA==", "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xml2js": { - "version": "0.6.1", - "license": "MIT", "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" + "@aws-sdk/client-sso": "3.658.1", + "@aws-sdk/token-providers": "3.654.0", + "@aws-sdk/types": "3.654.0", + "@smithy/property-provider": "^3.1.6", + "@smithy/shared-ini-file-loader": "^3.1.7", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4.0.0" + "node": ">=16.0.0" } }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.654.0.tgz", + "integrity": "sha512-6a2g9gMtZToqSu+CusjNK5zvbLJahQ9di7buO3iXgbizXpLXU1rnawCpWxwslMpT5fLgMSKDnKDrr6wdEk7jSw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/property-provider": "^3.1.6", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=4.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.654.0" } }, - "node_modules/xmlchars": { - "version": "2.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/xtend": { - "version": "4.0.2", - "dev": true, - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.654.0.tgz", + "integrity": "sha512-rxGgVHWKp8U2ubMv+t+vlIk7QYUaRCHaVpmUlJv0Wv6Q0KeO9a42T9FxHphjOTlCGQOLcjCreL9CF8Qhtb4mdQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/protocol-http": "^4.1.3", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.4" + "node": ">=16.0.0" } }, - "node_modules/y18n": { - "version": "5.0.8", - "license": "ISC", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/middleware-logger": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.654.0.tgz", + "integrity": "sha512-OQYb+nWlmASyXfRb989pwkJ9EVUMP1CrKn2eyTk3usl20JZmKo2Vjis6I0tLUkMSxMhnBJJlQKyWkRpD/u1FVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=10" + "node": ">=16.0.0" } }, - "node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "1.10.2", - "license": "ISC", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.654.0.tgz", + "integrity": "sha512-gKSomgltKVmsT8sC6W7CrADZ4GHwX9epk3GcH6QhebVO3LA9LRbkL3TwOPUXakxxOLLUTYdOZLIOtFf7iH00lg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/protocol-http": "^4.1.3", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">= 6" + "node": ">=16.0.0" } }, - "node_modules/yaml-ast-parser-custom-tags": { - "version": "0.0.43", - "license": "Apache-2.0" - }, - "node_modules/yaml-cfn": { - "version": "0.3.2", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.654.0.tgz", + "integrity": "sha512-liCcqPAyRsr53cy2tYu4qeH4MMN0eh9g6k56XzI5xd4SghXH5YWh4qOYAlQ8T66ZV4nPMtD8GLtLXGzsH8moFg==", "license": "Apache-2.0", "dependencies": { - "js-yaml": "^4.0.0" + "@aws-sdk/types": "3.654.0", + "@aws-sdk/util-endpoints": "3.654.0", + "@smithy/protocol-http": "^4.1.3", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" }, - "bin": { - "yaml-cfn": "cli.js" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/yaml-language-server": { - "version": "0.15.0", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.654.0.tgz", + "integrity": "sha512-ydGOrXJxj3x0sJhsXyTmvJVLAE0xxuTWFJihTl67RtaO7VRNtd82I3P3bwoMMaDn5WpmV5mPo8fEUDRlBm3fPg==", + "license": "Apache-2.0", "dependencies": { - "js-yaml": "^3.13.1", - "jsonc-parser": "^2.2.1", - "request-light": "^0.2.4", - "vscode-json-languageservice": "^3.10.0", - "vscode-languageserver": "^5.2.1", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-languageserver-types": "^3.15.1", - "vscode-nls": "^4.1.2", - "vscode-uri": "^2.1.1", - "yaml-language-server-parser": "0.1.2" - }, - "bin": { - "yaml-language-server": "bin/yaml-language-server" + "@aws-sdk/types": "3.654.0", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/types": "^3.4.2", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.6", + "tslib": "^2.6.2" }, "engines": { - "node": "*" - }, - "optionalDependencies": { - "prettier": "2.0.5" + "node": ">=16.0.0" } }, - "node_modules/yaml-language-server-parser": { - "version": "0.1.2", - "license": "Apache-2.0" - }, - "node_modules/yaml-language-server/node_modules/argparse": { - "version": "1.0.10", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/token-providers": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.654.0.tgz", + "integrity": "sha512-D8GeJYmvbfWkQDtTB4owmIobSMexZel0fOoetwvgCQ/7L8VPph3Q2bn1TRRIXvH7wdt6DcDxA3tKMHPBkT3GlA==", + "license": "Apache-2.0", "dependencies": { - "sprintf-js": "~1.0.2" + "@aws-sdk/types": "3.654.0", + "@smithy/property-provider": "^3.1.6", + "@smithy/shared-ini-file-loader": "^3.1.7", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.654.0" } }, - "node_modules/yaml-language-server/node_modules/js-yaml": { - "version": "3.14.1", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/types": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.654.0.tgz", + "integrity": "sha512-VWvbED3SV+10QJIcmU/PKjsKilsTV16d1I7/on4bvD/jo1qGeMXqLDBSen3ks/tuvXZF/mFc7ZW/W2DiLVtO7A==", + "license": "Apache-2.0", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/yaml-language-server/node_modules/jsonc-parser": { - "version": "2.3.1", - "license": "MIT" - }, - "node_modules/yaml-language-server/node_modules/prettier": { - "version": "2.0.5", - "license": "MIT", - "optional": true, - "bin": { - "prettier": "bin-prettier.js" + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/util-endpoints": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.654.0.tgz", + "integrity": "sha512-i902fcBknHs0Irgdpi62+QMvzxE+bczvILXigYrlHL4+PiEnlMVpni5L5W1qCkNZXf8AaMrSBuR1NZAGp6UOUw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/types": "^3.4.2", + "@smithy/util-endpoints": "^2.1.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10.13.0" + "node": ">=16.0.0" } }, - "node_modules/yaml-language-server/node_modules/vscode-json-languageservice": { - "version": "3.11.0", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.654.0.tgz", + "integrity": "sha512-ykYAJqvnxLt7wfrqya28wuH3/7NdrwzfiFd7NqEVQf7dXVxL5RPEpD7DxjcyQo3DsHvvdUvGZVaQhozycn1pzA==", + "license": "Apache-2.0", "dependencies": { - "jsonc-parser": "^3.0.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-languageserver-types": "3.16.0-next.2", - "vscode-nls": "^5.0.0", - "vscode-uri": "^2.1.2" + "@aws-sdk/types": "3.654.0", + "@smithy/types": "^3.4.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" } }, - "node_modules/yaml-language-server/node_modules/vscode-json-languageservice/node_modules/jsonc-parser": { - "version": "3.2.0", - "license": "MIT" - }, - "node_modules/yaml-language-server/node_modules/vscode-json-languageservice/node_modules/vscode-languageserver-types": { - "version": "3.16.0-next.2", - "license": "MIT" - }, - "node_modules/yaml-language-server/node_modules/vscode-json-languageservice/node_modules/vscode-nls": { - "version": "5.2.0", - "license": "MIT" - }, - "node_modules/yaml-language-server/node_modules/vscode-jsonrpc": { - "version": "4.0.0", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.654.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.654.0.tgz", + "integrity": "sha512-a0ojjdBN6pqv6gB4H/QPPSfhs7mFtlVwnmKCM/QrTaFzN0U810PJ1BST3lBx5sa23I5jWHGaoFY+5q65C3clLQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.654.0", + "@smithy/node-config-provider": "^3.1.7", + "@smithy/types": "^3.4.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=8.0.0 || >=10.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/yaml-language-server/node_modules/vscode-languageserver": { - "version": "5.2.1", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/abort-controller": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", + "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", + "license": "Apache-2.0", "dependencies": { - "vscode-languageserver-protocol": "3.14.1", - "vscode-uri": "^1.0.6" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/yaml-language-server/node_modules/vscode-languageserver-protocol": { - "version": "3.14.1", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz", + "integrity": "sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A==", + "license": "Apache-2.0", "dependencies": { - "vscode-jsonrpc": "^4.0.0", - "vscode-languageserver-types": "3.14.0" + "@smithy/protocol-http": "^4.1.4", + "@smithy/querystring-builder": "^3.0.7", + "@smithy/types": "^3.5.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" } }, - "node_modules/yaml-language-server/node_modules/vscode-languageserver-protocol/node_modules/vscode-languageserver-types": { - "version": "3.14.0", - "license": "MIT" - }, - "node_modules/yaml-language-server/node_modules/vscode-languageserver/node_modules/vscode-uri": { - "version": "1.0.8", - "license": "MIT" - }, - "node_modules/yaml-language-server/node_modules/vscode-nls": { - "version": "4.1.2", - "license": "MIT" + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/yargs": { - "version": "17.7.2", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/middleware-endpoint": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", + "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", + "license": "Apache-2.0", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "@smithy/core": "^2.5.1", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-middleware": "^3.0.8", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=16.0.0" } }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "dev": true, - "license": "ISC", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/middleware-retry": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz", + "integrity": "sha512-m1F70cPaMBML4HiTgCw5I+jFNtjgz5z5UdGnUbG37vw6kh4UvizFYjqJGHvicfgKMkDL6mXwyPp5mhZg02g5sg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.5", + "@smithy/service-error-classification": "^3.0.8", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-retry": "^3.0.8", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, "engines": { - "node": ">=10" + "node": ">=16.0.0" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "dev": true, - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/middleware-serde": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", + "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", + "license": "Apache-2.0", "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=16.0.0" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/middleware-stack": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", + "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, - "node_modules/yargs/node_modules/cliui": { - "version": "8.0.1", - "license": "ISC", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", + "license": "Apache-2.0", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=16.0.0" } }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/node-http-handler": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", + "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^5.0.1" + "@smithy/abort-controller": "^3.1.6", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "21.1.1", - "license": "ISC", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12" + "node": ">=16.0.0" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "dev": true, - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "license": "Apache-2.0", "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/yazl": { - "version": "2.5.1", - "dev": true, - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/querystring-builder": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", + "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", + "license": "Apache-2.0", "dependencies": { - "buffer-crc32": "~0.2.3" - } - }, - "node_modules/ylru": { - "version": "1.4.0", - "dev": true, - "license": "MIT", + "@smithy/types": "^3.6.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">= 4.0.0" + "node": ">=16.0.0" } }, - "node_modules/yn": { - "version": "3.1.1", - "dev": true, - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/querystring-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", + "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6" + "node": ">=16.0.0" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/service-error-classification": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz", + "integrity": "sha512-uEC/kCCFto83bz5ZzapcrgGqHOh/0r69sZ2ZuHlgoD5kYgXJEThCoTuw/y1Ub3cE7aaKdznb+jD9xRPIfIwD7g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/zip-stream": { - "version": "6.0.1", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "license": "Apache-2.0", "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 14" + "node": ">=16.0.0" } }, - "node_modules/zip-stream/node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/smithy-client": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", + "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", + "license": "Apache-2.0", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/zip-stream/node_modules/events": { - "version": "3.3.0", - "license": "MIT", + "@smithy/core": "^2.5.1", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-stream": "^3.2.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.8.x" + "node": ">=16.0.0" } }, - "node_modules/zip-stream/node_modules/ieee754": { - "version": "1.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/zip-stream/node_modules/readable-stream": { - "version": "4.5.2", - "license": "MIT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "tslib": "^2.6.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=16.0.0" } }, - "packages/amazonq": { - "name": "amazon-q-vscode", - "version": "1.28.0-SNAPSHOT", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/url-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", + "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", "license": "Apache-2.0", "dependencies": { - "aws-core-vscode": "file:../core/" - }, - "devDependencies": {}, - "engines": { - "npm": "^10.1.0", - "vscode": "^1.68.0" + "@smithy/querystring-parser": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" } }, - "packages/core": { - "name": "aws-core-vscode", - "version": "1.0.0", - "hasInstallScript": true, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "license": "Apache-2.0", "dependencies": { - "@amzn/codewhisperer-streaming": "file:../../src.gen/@amzn/codewhisperer-streaming", - "@aws-sdk/client-cognito-identity": "^3.637.0", - "@aws-sdk/client-lambda": "^3.637.0", - "@aws-sdk/client-sso": "^3.342.0", - "@aws-sdk/client-sso-oidc": "^3.574.0", - "@aws-sdk/credential-provider-ini": "3.46.0", - "@aws-sdk/credential-provider-process": "3.37.0", - "@aws-sdk/credential-provider-sso": "^3.345.0", - "@aws-sdk/property-provider": "3.46.0", - "@aws-sdk/smithy-client": "^3.46.0", - "@aws-sdk/util-arn-parser": "^3.46.0", - "@aws/mynah-ui": "^4.15.11", - "@gerhobbelt/gitignore-parser": "^0.2.0-9", - "@iarna/toml": "^2.2.5", - "@smithy/middleware-retry": "^2.3.1", - "@smithy/protocol-http": "^3.3.0", - "@smithy/service-error-classification": "^2.1.5", - "@smithy/shared-ini-file-loader": "^2.2.8", - "@smithy/util-retry": "^2.2.0", - "@vscode/debugprotocol": "^1.57.0", - "adm-zip": "^0.5.10", - "amazon-states-language-service": "^1.11.0", - "archiver": "^7.0.1", - "async-lock": "^1.4.0", - "aws-sdk": "^2.1384.0", - "aws-ssm-document-language-service": "^1.0.0", - "bytes": "^3.1.2", - "cross-fetch": "^4.0.0", - "cross-spawn": "^7.0.3", - "fast-json-patch": "^3.1.1", - "fs-extra": "^10.0.1", - "glob": "^10.3.10", - "got": "^11.8.5", - "highlight.js": "^11.9.0", - "i18n-ts": "^1.0.5", - "immutable": "^4.3.0", - "jose": "5.4.1", - "js-yaml": "^4.1.0", - "jsonc-parser": "^3.2.0", - "lodash": "^4.17.21", - "markdown-it": "^13.0.2", - "mime-types": "^2.1.32", - "node-fetch": "^2.7.0", - "portfinder": "^1.0.32", - "proper-lockfile": "^4.1.2", - "ps-list": "^8.1.1", - "semver": "^7.5.4", - "stream-buffers": "^3.0.2", - "strip-ansi": "^5.2.0", - "tcp-port-used": "^1.0.1", - "vscode-languageclient": "^6.1.4", - "vscode-languageserver": "^6.1.1", - "vscode-languageserver-protocol": "^3.15.3", - "vscode-languageserver-textdocument": "^1.0.8", - "vue": "^3.3.4", - "web-tree-sitter": "^0.20.8", - "whatwg-url": "^14.0.0", - "winston": "^3.11.0", - "winston-transport": "^4.6.0", - "xml2js": "^0.6.1", - "yaml-cfn": "^0.3.2" + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, - "devDependencies": { - "@aws-sdk/types": "^3.13.1", - "@aws/fully-qualified-names": "^2.1.4", - "@cspotcode/source-map-support": "^0.8.1", - "@sinonjs/fake-timers": "^10.0.2", - "@types/adm-zip": "^0.4.34", - "@types/archiver": "^6.0.2", - "@types/async-lock": "^1.4.0", - "@types/bytes": "^3.1.0", - "@types/circular-dependency-plugin": "^5.0.8", - "@types/cross-spawn": "^6.0.6", - "@types/diff": "^5.0.7", - "@types/fs-extra": "^9.0.11", - "@types/glob": "^8.1.0", - "@types/js-yaml": "^4.0.5", - "@types/jsdom": "^21.1.6", - "@types/lodash": "^4.14.180", - "@types/markdown-it": "^13.0.2", - "@types/mime-types": "^2.1.4", - "@types/mocha": "^10.0.6", - "@types/node": "^16.18.95", - "@types/node-fetch": "^2.6.8", - "@types/prismjs": "^1.26.0", - "@types/proper-lockfile": "^4.1.4", - "@types/readline-sync": "^1.4.3", - "@types/semver": "^7.5.0", - "@types/sinon": "^10.0.5", - "@types/sinonjs__fake-timers": "^8.1.2", - "@types/stream-buffers": "^3.0.7", - "@types/tcp-port-used": "^1.0.1", - "@types/uuid": "^9.0.1", - "@types/whatwg-url": "^11.0.4", - "@types/xml2js": "^0.4.11", - "@vue/compiler-sfc": "^3.3.2", - "c8": "^9.0.0", - "circular-dependency-plugin": "^5.2.2", - "css-loader": "^6.10.0", - "diff": "^5.1.0", - "esbuild-loader": "2.20.0", - "file-loader": "^6.2.0", - "jsdom": "^23.0.1", - "json-schema-to-typescript": "^13.1.1", - "marked": "^13.0.1", - "mocha": "^10.1.0", - "mocha-junit-reporter": "^2.2.1", - "mocha-multi-reporters": "^1.5.1", - "readline-sync": "^1.4.9", - "sass": "^1.49.8", - "sass-loader": "^12.6.0", - "sinon": "^14.0.0", - "style-loader": "^3.3.1", - "ts-node": "^10.9.1", - "typescript": "^5.0.4", - "umd-compat-loader": "^2.1.2", - "vue-loader": "^17.2.2", - "vue-style-loader": "^4.1.3", - "webfont": "^11.2.26" + "engines": { + "node": ">=16.0.0" + } + }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "npm": "^10.1.0", - "vscode": "^1.68.0" + "node": ">=16.0.0" } }, - "packages/core/node_modules/@types/node": { - "version": "16.18.95", - "dev": true, - "license": "MIT" + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } }, - "packages/core/src/web": { - "name": "web-toolkit", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", "license": "Apache-2.0", "dependencies": { - "crypto-browserify": "^3.12.0", - "os-browserify": "^0.3.0", - "path-browserify": "^1.0.1", - "process": "^0.11.10", - "stream-browserify": "^3.0.0" + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, - "devDependencies": { - "assert": "^2.1.0" + "engines": { + "node": ">=16.0.0" } }, - "packages/toolkit": { - "name": "aws-toolkit-vscode", - "version": "3.27.0-SNAPSHOT", - "engines": "This field will be autopopulated from the core module during debugging and packaging.", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-retry": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz", + "integrity": "sha512-TCEhLnY581YJ+g1x0hapPz13JFqzmh/pMWL2KEFASC51qCfw3+Y47MrTmea4bUE5vsdxQ4F6/KFbUeSz22Q1ow==", "license": "Apache-2.0", "dependencies": { - "aws-core-vscode": "file:../core/" + "@smithy/service-error-classification": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" }, - "devDependencies": {} + "engines": { + "node": ">=16.0.0" + } }, - "plugins/eslint-plugin-aws-toolkits": { - "version": "1.0.0", + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", + "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", "license": "Apache-2.0", - "devDependencies": { - "mocha": "^10.1.0" + "dependencies": { + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "npm": "^10.1.0" + "node": ">=16.0.0" + } + }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", + "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@tsconfig/node16": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.3.tgz", + "integrity": "sha512-9nTOUBn+EMKO6rtSZJk+DcqsfgtlERGT9XPJ5PRj/HNENPCBY1yu/JEj5wT6GLtbCLBO2k46SeXDaY0pjMqypw==", + "dev": true, + "license": "MIT" + }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@types/node": { + "version": "16.18.115", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.115.tgz", + "integrity": "sha512-NF5ajYn+dq0tRfswdyp8Df75h7D9z+L8TCIwrXoh46ZLK6KZVXkRhf/luXaZytvm/keUo9vU4m1Bg39St91a5w==", + "dev": true, + "license": "MIT" + }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" } }, "src.gen/@amzn/codewhisperer-streaming": { @@ -19795,17 +22577,6 @@ "tslib": "^2.3.1" } }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/@babel/runtime": { - "version": "7.25.6", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/abort-controller": { "version": "2.2.0", "license": "Apache-2.0", @@ -20320,201 +23091,37 @@ "license": "MIT" }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@types/uuid": { - "version": "8.3.4", - "dev": true, - "license": "MIT" - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/bowser": { - "version": "2.11.0", - "license": "MIT" - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/cliui": { - "version": "7.0.4", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/concurrently": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "date-fns": "^2.16.1", - "lodash": "^4.17.21", - "rxjs": "^6.6.3", - "spawn-command": "^0.0.2-1", - "supports-color": "^8.1.0", - "tree-kill": "^1.2.2", - "yargs": "^16.2.0" - }, - "bin": { - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.0 || >=16.0.0" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/date-fns": { - "version": "2.30.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/downlevel-dts": { - "version": "0.10.1", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.2", - "shelljs": "^0.8.3", - "typescript": "next" - }, - "bin": { - "downlevel-dts": "index.js" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/downlevel-dts/node_modules/typescript": { - "version": "5.7.0-dev.20240926", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } + "version": "8.3.4", + "dev": true, + "license": "MIT" }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/emoji-regex": { - "version": "8.0.0", + "src.gen/@amzn/codewhisperer-streaming/node_modules/balanced-match": { + "version": "1.0.2", "dev": true, "license": "MIT" }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/escalade": { - "version": "3.2.0", + "src.gen/@amzn/codewhisperer-streaming/node_modules/bowser": { + "version": "2.11.0", + "license": "MIT" + }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/brace-expansion": { + "version": "2.0.1", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "balanced-match": "^1.0.0" } }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/fs.realpath": { "version": "1.0.0", "dev": true, "license": "ISC" }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/function-bind": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "src.gen/@amzn/codewhisperer-streaming/node_modules/glob": { "version": "7.2.3", "dev": true, @@ -20554,25 +23161,6 @@ "node": "*" } }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/hasown": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "src.gen/@amzn/codewhisperer-streaming/node_modules/inflight": { "version": "1.0.6", "dev": true, @@ -20587,46 +23175,11 @@ "dev": true, "license": "ISC" }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/interpret": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/is-core-module": { - "version": "2.15.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "src.gen/@amzn/codewhisperer-streaming/node_modules/jsonc-parser": { "version": "3.3.1", "dev": true, "license": "MIT" }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/lodash": { - "version": "4.17.21", - "dev": true, - "license": "MIT" - }, "src.gen/@amzn/codewhisperer-streaming/node_modules/lunr": { "version": "2.3.9", "dev": true, @@ -20670,50 +23223,6 @@ "node": ">=0.10.0" } }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/rechoir": { - "version": "0.6.2", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/regenerator-runtime": { - "version": "0.14.1", - "dev": true, - "license": "MIT" - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/resolve": { - "version": "1.22.8", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "src.gen/@amzn/codewhisperer-streaming/node_modules/rimraf": { "version": "3.0.2", "dev": true, @@ -20728,49 +23237,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/rxjs": { - "version": "6.6.7", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "dev": true, - "license": "0BSD" - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/semver": { - "version": "7.6.3", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/shelljs": { - "version": "0.8.5", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, "src.gen/@amzn/codewhisperer-streaming/node_modules/shiki": { "version": "0.11.1", "dev": true, @@ -20781,67 +23247,6 @@ "vscode-textmate": "^6.0.0" } }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/spawn-command": { - "version": "0.0.2", - "dev": true - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/tree-kill": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, "src.gen/@amzn/codewhisperer-streaming/node_modules/tslib": { "version": "2.7.0", "license": "0BSD" @@ -20895,59 +23300,10 @@ "dev": true, "license": "MIT" }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "src.gen/@amzn/codewhisperer-streaming/node_modules/wrappy": { "version": "1.0.2", "dev": true, "license": "ISC" - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/yargs": { - "version": "16.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "src.gen/@amzn/codewhisperer-streaming/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } } } } diff --git a/package.json b/package.json index 01b33ff6762..e658a3a916e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "scripts": { "prepare": "ts-node ./scripts/prepare.ts", - "preinstall": "cd src.gen/@amzn/codewhisperer-streaming && npm i", + "preinstall": "cd src.gen/@amzn/codewhisperer-streaming && npm i && cd ../amazon-q-developer-streaming-client && npm i", "postinstall": "npm run buildCustomLintPlugin && npm run postinstall -ws --if-present", "buildCustomLintPlugin": "npm run build -w plugins/eslint-plugin-aws-toolkits", "compile": "npm run compile -w packages/", @@ -39,11 +39,13 @@ "generateNonCodeFiles": "npm run generateNonCodeFiles -w packages/ --if-present" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.258", + "@aws-toolkits/telemetry": "^1.0.289", "@playwright/browser-chromium": "^1.43.1", + "@stylistic/eslint-plugin": "^2.11.0", + "@types/he": "^1.2.3", "@types/vscode": "^1.68.0", "@types/vscode-webview": "^1.57.1", - "@types/webpack-env": "^1.18.1", + "@types/webpack-env": "^1.18.5", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", "@vscode/codicons": "^0.0.33", @@ -58,17 +60,18 @@ "eslint-plugin-security-node": "^1.1.4", "eslint-plugin-unicorn": "^54.0.0", "husky": "^9.0.7", - "prettier": "^3.3.2", + "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", "ts-node": "^10.9.1", "typescript": "^5.0.4", - "webpack": "^5.83.0", + "webpack": "^5.95.0", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1", + "webpack-dev-server": "^4.15.2", "webpack-merge": "^5.10.0" }, "dependencies": { + "@types/node": "^22.7.5", "vscode-nls": "^5.2.0", "vscode-nls-dev": "^4.0.4" } diff --git a/packages/amazonq/.changes/1.28.0.json b/packages/amazonq/.changes/1.28.0.json new file mode 100644 index 00000000000..481a4d8e7d2 --- /dev/null +++ b/packages/amazonq/.changes/1.28.0.json @@ -0,0 +1,18 @@ +{ + "date": "2024-10-03", + "version": "1.28.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Amazon Q /dev: define first folder as a root path for LLM-created files when using workspaces" + }, + { + "type": "Feature", + "description": "Amazon Q Code Transformation: allow users to skip running tests" + }, + { + "type": "Feature", + "description": "Amazon Q Developer: Updated legal disclaimer text" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.29.0.json b/packages/amazonq/.changes/1.29.0.json new file mode 100644 index 00000000000..35cc08b1b25 --- /dev/null +++ b/packages/amazonq/.changes/1.29.0.json @@ -0,0 +1,54 @@ +{ + "date": "2024-10-10", + "version": "1.29.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Amazon Q /dev: include telemetry for workspace usage when generating new files" + }, + { + "type": "Bug Fix", + "description": "Amazon Q extension may fail to start if AWS Toolkit extension fails to start" + }, + { + "type": "Bug Fix", + "description": "Start language server by default" + }, + { + "type": "Bug Fix", + "description": "Amazon Q Feature Dev: Add error messages when the upload URL expires" + }, + { + "type": "Bug Fix", + "description": "Amazon Q (/dev): view diffs of previous /dev iterations" + }, + { + "type": "Bug Fix", + "description": "Q dev handle no change required" + }, + { + "type": "Deprecation", + "description": "The next release of this extension will require VS Code 1.83.0 or newer." + }, + { + "type": "Feature", + "description": "Automatically pause and resume @workspace indexing when OS CPU load is high" + }, + { + "type": "Feature", + "description": "Add buttons to code blocks to view and accept diffs." + }, + { + "type": "Feature", + "description": "Inline completion for more json files, and all yaml files" + }, + { + "type": "Feature", + "description": "Show a one-time warning if new VS Code is required" + }, + { + "type": "Removal", + "description": "Minimum required VSCode version is now 1.83" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.30.0.json b/packages/amazonq/.changes/1.30.0.json new file mode 100644 index 00000000000..070f0f7be5f --- /dev/null +++ b/packages/amazonq/.changes/1.30.0.json @@ -0,0 +1,10 @@ +{ + "date": "2024-10-17", + "version": "1.30.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Various fixes and changes" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.31.0.json b/packages/amazonq/.changes/1.31.0.json new file mode 100644 index 00000000000..882fbad0f49 --- /dev/null +++ b/packages/amazonq/.changes/1.31.0.json @@ -0,0 +1,34 @@ +{ + "date": "2024-10-29", + "version": "1.31.0", + "entries": [ + { + "type": "Breaking Change", + "description": "Change keybind for focusing chat to ctrl+win+i on Windows, ctrl+cmd+i on macOS and ctrl+meta+i on Linux" + }, + { + "type": "Bug Fix", + "description": "Inline Suggestions: Occasional `ValidationException` if user context has too many characters." + }, + { + "type": "Bug Fix", + "description": "Update `@workspace` index when adding or deleting a file" + }, + { + "type": "Bug Fix", + "description": "fixed device code detection when running auth through tunneled vscode" + }, + { + "type": "Feature", + "description": "Use inline chat to select code and transform it with natural language instructions" + }, + { + "type": "Feature", + "description": "Amazon Q /dev: Add stop generation action" + }, + { + "type": "Feature", + "description": "Provide more frequent updates about code changes made by agent" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.32.0.json b/packages/amazonq/.changes/1.32.0.json new file mode 100644 index 00000000000..cfa6f4796bf --- /dev/null +++ b/packages/amazonq/.changes/1.32.0.json @@ -0,0 +1,26 @@ +{ + "date": "2024-10-29", + "version": "1.32.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Remove warning when no input is provided to inline chat input box" + }, + { + "type": "Bug Fix", + "description": "Use Sagemaker environment IAM Credentials for Code Completion when they're available" + }, + { + "type": "Bug Fix", + "description": "Inline: Code completion not working for Sagemaker Pro Tier users." + }, + { + "type": "Bug Fix", + "description": "Disable /transform and /dev commands for sagemaker users as they're not supported" + }, + { + "type": "Feature", + "description": "Amazon SageMaker Studio: Enable Free Tier Chat for IAM users" + } + ] +} diff --git a/packages/amazonq/.changes/1.33.0.json b/packages/amazonq/.changes/1.33.0.json new file mode 100644 index 00000000000..ac740352518 --- /dev/null +++ b/packages/amazonq/.changes/1.33.0.json @@ -0,0 +1,10 @@ +{ + "date": "2024-10-30", + "version": "1.33.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Amazon Q /dev: fix for stop button showing for Code Transformation" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.34.0.json b/packages/amazonq/.changes/1.34.0.json new file mode 100644 index 00000000000..35d1a3b1e1d --- /dev/null +++ b/packages/amazonq/.changes/1.34.0.json @@ -0,0 +1,22 @@ +{ + "date": "2024-11-07", + "version": "1.34.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Align example help text with prompt message in chat" + }, + { + "type": "Bug Fix", + "description": "Improve `@workspace` index auto pause start strategy. " + }, + { + "type": "Feature", + "description": "Allow users to View and Apply diff when they explictily send code to Amazon Q using - Fix, Refactor, Optimize and Send To Prompt." + }, + { + "type": "Feature", + "description": "Security Scan: Auto-scan now supports JSX, TSX, Kotlin, Scala, and Shell files." + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.35.0.json b/packages/amazonq/.changes/1.35.0.json new file mode 100644 index 00000000000..45b791ac4c4 --- /dev/null +++ b/packages/amazonq/.changes/1.35.0.json @@ -0,0 +1,18 @@ +{ + "date": "2024-11-11", + "version": "1.35.0", + "entries": [ + { + "type": "Breaking Change", + "description": "Change focus chat keybind to win+alt+i on Windows, cmd+alt+i on macOS, and meta+alt+i on Linux" + }, + { + "type": "Bug Fix", + "description": "Fix suboptimal inline suggestions from Amazon Q caused by improperly formatted supplemental context" + }, + { + "type": "Bug Fix", + "description": "Fix empty chunks being sent to service and get validationException" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.36.0.json b/packages/amazonq/.changes/1.36.0.json new file mode 100644 index 00000000000..0b10e6d8720 --- /dev/null +++ b/packages/amazonq/.changes/1.36.0.json @@ -0,0 +1,22 @@ +{ + "date": "2024-11-14", + "version": "1.36.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Fix broken inline suggestion auto-trigger on Systemverfilog files if users dont have systemverilog extension installed and enabled" + }, + { + "type": "Bug Fix", + "description": "tutorial always showing on start" + }, + { + "type": "Feature", + "description": "Enable default `@workspace` context of Amazon Q chat for certain users" + }, + { + "type": "Feature", + "description": "Amazon Q /dev: Add an action to accept individual files" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.37.0.json b/packages/amazonq/.changes/1.37.0.json new file mode 100644 index 00000000000..3076836aa3c --- /dev/null +++ b/packages/amazonq/.changes/1.37.0.json @@ -0,0 +1,50 @@ +{ + "date": "2024-11-22", + "version": "1.37.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Amazon Q Feature Dev: display limit reached error message" + }, + { + "type": "Bug Fix", + "description": "Amazon Q chat: `@workspace` command shown in all tab types" + }, + { + "type": "Bug Fix", + "description": "Chat container exceeds width of container" + }, + { + "type": "Bug Fix", + "description": "amazon q inline: skip indexing when no workspace folders are found" + }, + { + "type": "Bug Fix", + "description": "file details and name unneccessary cropping" + }, + { + "type": "Bug Fix", + "description": "Amazon Q /dev: update diff window behavior after a change is accepted" + }, + { + "type": "Feature", + "description": "Amazon Q /dev: support `.gradle` files" + }, + { + "type": "Feature", + "description": "Code Transform: Enable support for Java 17 projects." + }, + { + "type": "Feature", + "description": "Notifications: Support for delivering critical alerts and product updates" + }, + { + "type": "Feature", + "description": "Retrieve and display a customization name when a customization is overridden in an AB test" + }, + { + "type": "Feature", + "description": "Feature(Amazon Q Code Transformation): support conversions of embedded SQL from Oracle to PostgreSQL" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.38.0.json b/packages/amazonq/.changes/1.38.0.json new file mode 100644 index 00000000000..cc04393dfa7 --- /dev/null +++ b/packages/amazonq/.changes/1.38.0.json @@ -0,0 +1,18 @@ +{ + "date": "2024-11-27", + "version": "1.38.0", + "entries": [ + { + "type": "Feature", + "description": "Amazon Q /dev: support `Dockerfile` files" + }, + { + "type": "Feature", + "description": "Introduce @workspace command to enhance context fetching for Chat" + }, + { + "type": "Feature", + "description": "Feature(Amazon Q Code Transformation): allow users to view results in 5 smaller diffs" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.39.0.json b/packages/amazonq/.changes/1.39.0.json new file mode 100644 index 00000000000..750b30ba03a --- /dev/null +++ b/packages/amazonq/.changes/1.39.0.json @@ -0,0 +1,34 @@ +{ + "date": "2024-12-03", + "version": "1.39.0", + "entries": [ + { + "type": "Feature", + "description": "Added a getting started page for exploring amazon q agents" + }, + { + "type": "Feature", + "description": "`/test` in Q chat to generate unit tests for java and python" + }, + { + "type": "Feature", + "description": "`/doc` in Q chat to generate and update documentation for your project" + }, + { + "type": "Feature", + "description": "Amazon Q Code Scan is now Amazon Q Code Review" + }, + { + "type": "Feature", + "description": "`/review` in Q chat to scan your code for vulnerabilities and quality issues, and generate fixes" + }, + { + "type": "Feature", + "description": "Security Scan: New TreeView to display security scan issues and vulnerabilities detected in your project. The TreeView provides an organized and hierarchical view of the scan results, making it easier to navigate and prioritize the issues that need to be addressed." + }, + { + "type": "Feature", + "description": "Security Scan: Added ability to suppress or ignore security issues" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.40.0.json b/packages/amazonq/.changes/1.40.0.json new file mode 100644 index 00000000000..ea251bdd8fe --- /dev/null +++ b/packages/amazonq/.changes/1.40.0.json @@ -0,0 +1,66 @@ +{ + "date": "2024-12-10", + "version": "1.40.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Improved LLM code review for file review." + }, + { + "type": "Bug Fix", + "description": "@workspace is missing from the welcome to q chat tab" + }, + { + "type": "Bug Fix", + "description": "Fix chat syntax highlighting when using several different themes" + }, + { + "type": "Bug Fix", + "description": "Amazon Q /doc: progress bar persists after cancelling README creation" + }, + { + "type": "Bug Fix", + "description": "Code Review: Fixed a bug where some issues are missing from the code issues view for workspaces with custom names" + }, + { + "type": "Bug Fix", + "description": "Amazon Q /doc: Prompt user to choose a folder in chat" + }, + { + "type": "Bug Fix", + "description": "Amazon Q /dev not adding Dockerfiles in nested folders" + }, + { + "type": "Bug Fix", + "description": "Improved Code Fix generation for code review issues" + }, + { + "type": "Bug Fix", + "description": "Fix the quick start buttons on the explore page to show amazon q colours on hover" + }, + { + "type": "Feature", + "description": "Q feature dev: recognize .bms, .pli code files" + }, + { + "type": "Feature", + "description": "Amazon Q Code Transformation: show job ID in Transformation Hub" + }, + { + "type": "Feature", + "description": "UI improvements to Amazon Q Chat: New splash loader animation, initial streaming card animation, improved button colours" + }, + { + "type": "Feature", + "description": "Add acknowledgement button for amazon q chat disclaimer" + }, + { + "type": "Feature", + "description": "Navigate through prompt history by using the up/down arrows" + }, + { + "type": "Feature", + "description": "Amazon Q: Simplify log channel" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.41.0.json b/packages/amazonq/.changes/1.41.0.json new file mode 100644 index 00000000000..d0ca09cb476 --- /dev/null +++ b/packages/amazonq/.changes/1.41.0.json @@ -0,0 +1,62 @@ +{ + "date": "2024-12-17", + "version": "1.41.0", + "entries": [ + { + "type": "Bug Fix", + "description": "/review: Apply fix removes other issues in the same file." + }, + { + "type": "Bug Fix", + "description": "Fix(Amazon Q Code Transformation): show correct diff when running consecutive transformations" + }, + { + "type": "Bug Fix", + "description": "Improve when the welcome page is shown in amazon q chat" + }, + { + "type": "Bug Fix", + "description": "Code Review: Cleaned up output logs when running /review" + }, + { + "type": "Bug Fix", + "description": "Code Review: Fixed a bug where applying a fix did not update the positions of other issues in the same file." + }, + { + "type": "Bug Fix", + "description": "Chat: When navigating to previous prompts, code attachments are sometimes displayed incorrectly" + }, + { + "type": "Bug Fix", + "description": "/review: Diagnostics in the problems panel are mapped to the wrong code" + }, + { + "type": "Bug Fix", + "description": "Fix opentabs context possibly timeout due to race condition of misuse of different timeout functionalities" + }, + { + "type": "Bug Fix", + "description": "Auth: SSO session was bad, but no reauth prompt given" + }, + { + "type": "Bug Fix", + "description": "Reduce frequency of system status poll" + }, + { + "type": "Bug Fix", + "description": "Chat: When writing a prompt without sending it, navigating via up/down arrows sometimes deletes the unsent prompt." + }, + { + "type": "Bug Fix", + "description": "Code Review: Fixed a bug where projects with repeated path names did not scan properly." + }, + { + "type": "Feature", + "description": "/review: Code fix automatically scrolls into view after generation." + }, + { + "type": "Feature", + "description": "Chat: improve font size and line-height in footer (below prompt input field)" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/next-release/Bug Fix-525ae1a7-9ded-4bd1-8357-0a4c0e7fe33f.json b/packages/amazonq/.changes/next-release/Bug Fix-525ae1a7-9ded-4bd1-8357-0a4c0e7fe33f.json new file mode 100644 index 00000000000..f81d5e31a14 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-525ae1a7-9ded-4bd1-8357-0a4c0e7fe33f.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Amazon Q Code Transformation: allow POSTGRESQL as target DB for SQL conversions" +} diff --git a/packages/amazonq/.changes/next-release/Bug Fix-93a879d3-5fa1-47d3-a908-77fcbce9335d.json b/packages/amazonq/.changes/next-release/Bug Fix-93a879d3-5fa1-47d3-a908-77fcbce9335d.json deleted file mode 100644 index f86e100111a..00000000000 --- a/packages/amazonq/.changes/next-release/Bug Fix-93a879d3-5fa1-47d3-a908-77fcbce9335d.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Bug Fix", - "description": "Amazon Q /dev: define first folder as a root path for LLM-created files when using workspaces" -} diff --git a/packages/amazonq/.changes/next-release/Bug Fix-c0428af9-a9e5-4f7f-a4f3-9cfe4ea851c9.json b/packages/amazonq/.changes/next-release/Bug Fix-c0428af9-a9e5-4f7f-a4f3-9cfe4ea851c9.json new file mode 100644 index 00000000000..bc80e2916c3 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-c0428af9-a9e5-4f7f-a4f3-9cfe4ea851c9.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "/review: Code block extends beyond page margins in code issue detail view" +} diff --git a/packages/amazonq/.changes/next-release/Bug Fix-f7bb3f35-1b43-4604-b3bd-c2e7f1b65083.json b/packages/amazonq/.changes/next-release/Bug Fix-f7bb3f35-1b43-4604-b3bd-c2e7f1b65083.json new file mode 100644 index 00000000000..2f917f23204 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-f7bb3f35-1b43-4604-b3bd-c2e7f1b65083.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Amazon Q Code Transformation: retry project upload up to 3 times" +} diff --git a/packages/amazonq/.vscode/launch.json b/packages/amazonq/.vscode/launch.json index a1fde10e9ef..120eacdb44d 100644 --- a/packages/amazonq/.vscode/launch.json +++ b/packages/amazonq/.vscode/launch.json @@ -99,6 +99,25 @@ "order": 2 } }, + { + "name": "Extension Tests (web)", + "type": "extensionHost", + "debugWebWorkerHost": true, + "request": "launch", + "args": [ + "--disable-extension=amazonwebservices.aws-toolkit-vscode", + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionDevelopmentKind=web", + "--extensionTestsPath=${workspaceFolder}/dist/test/web/testRunnerWebCore", + "${workspaceFolder}/../core/dist/src/testFixtures/workspaceFolder" + ], + "outFiles": ["${workspaceFolder}/dist/**/*.js", "${workspaceFolder}/../core/dist/**/*.js"], + "preLaunchTask": "webRun", + "presentation": { + "group": "3_ExtensionTests", + "order": 3 + } + }, { "name": "E2E Test (current file)", "type": "extensionHost", diff --git a/packages/amazonq/.vscode/tasks.json b/packages/amazonq/.vscode/tasks.json index 0647eb8792a..9fefd8ec182 100644 --- a/packages/amazonq/.vscode/tasks.json +++ b/packages/amazonq/.vscode/tasks.json @@ -109,7 +109,6 @@ // Since `webpack --watch` never terminates (but finishes packaging at some point), // VS Code uses this to parse the CLI output to pattern match something that indicates it is done "problemMatcher": "$ts-webpack-watch", - "dependsOn": ["copyPackageJson"], "options": { "cwd": "${workspaceFolder}/../../packages/core" } @@ -124,7 +123,7 @@ "label": "webRunTerminate", "command": "echo ${input:webRunTerminate}", "type": "shell", - "dependsOn": ["restorePackageJson"] + "dependsOn": [] }, // ---------- End: Web Mode Tasks ---------- { diff --git a/packages/amazonq/CHANGELOG.md b/packages/amazonq/CHANGELOG.md index d883a17d0a4..a53d213ef43 100644 --- a/packages/amazonq/CHANGELOG.md +++ b/packages/amazonq/CHANGELOG.md @@ -1,3 +1,135 @@ +## 1.41.0 2024-12-17 + +- **Bug Fix** /review: Apply fix removes other issues in the same file. +- **Bug Fix** Fix(Amazon Q Code Transformation): show correct diff when running consecutive transformations +- **Bug Fix** Improve when the welcome page is shown in amazon q chat +- **Bug Fix** Code Review: Cleaned up output logs when running /review +- **Bug Fix** Code Review: Fixed a bug where applying a fix did not update the positions of other issues in the same file. +- **Bug Fix** Chat: When navigating to previous prompts, code attachments are sometimes displayed incorrectly +- **Bug Fix** /review: Diagnostics in the problems panel are mapped to the wrong code +- **Bug Fix** Fix opentabs context possibly timeout due to race condition of misuse of different timeout functionalities +- **Bug Fix** Auth: SSO session was bad, but no reauth prompt given +- **Bug Fix** Reduce frequency of system status poll +- **Bug Fix** Chat: When writing a prompt without sending it, navigating via up/down arrows sometimes deletes the unsent prompt. +- **Bug Fix** Code Review: Fixed a bug where projects with repeated path names did not scan properly. +- **Feature** /review: Code fix automatically scrolls into view after generation. +- **Feature** Chat: improve font size and line-height in footer (below prompt input field) + +## 1.40.0 2024-12-10 + +- **Bug Fix** Improved LLM code review for file review. +- **Bug Fix** @workspace is missing from the welcome to q chat tab +- **Bug Fix** Fix chat syntax highlighting when using several different themes +- **Bug Fix** Amazon Q /doc: progress bar persists after cancelling README creation +- **Bug Fix** Code Review: Fixed a bug where some issues are missing from the code issues view for workspaces with custom names +- **Bug Fix** Amazon Q /doc: Prompt user to choose a folder in chat +- **Bug Fix** Amazon Q /dev not adding Dockerfiles in nested folders +- **Bug Fix** Improved Code Fix generation for code review issues +- **Bug Fix** Fix the quick start buttons on the explore page to show amazon q colours on hover +- **Feature** Q feature dev: recognize .bms, .pli code files +- **Feature** Amazon Q Code Transformation: show job ID in Transformation Hub +- **Feature** UI improvements to Amazon Q Chat: New splash loader animation, initial streaming card animation, improved button colours +- **Feature** Add acknowledgement button for amazon q chat disclaimer +- **Feature** Navigate through prompt history by using the up/down arrows +- **Feature** Amazon Q: Simplify log channel + +## 1.39.0 2024-12-03 + +- **Feature** Added a getting started page for exploring amazon q agents +- **Feature** `/test` in Q chat to generate unit tests for java and python +- **Feature** `/doc` in Q chat to generate and update documentation for your project +- **Feature** Amazon Q Code Scan is now Amazon Q Code Review +- **Feature** `/review` in Q chat to scan your code for vulnerabilities and quality issues, and generate fixes +- **Feature** Security Scan: New TreeView to display security scan issues and vulnerabilities detected in your project. The TreeView provides an organized and hierarchical view of the scan results, making it easier to navigate and prioritize the issues that need to be addressed. +- **Feature** Security Scan: Added ability to suppress or ignore security issues + +## 1.38.0 2024-11-27 + +- **Feature** Amazon Q /dev: support `Dockerfile` files +- **Feature** Introduce @workspace command to enhance context fetching for Chat +- **Feature** Feature(Amazon Q Code Transformation): allow users to view results in 5 smaller diffs + +## 1.37.0 2024-11-22 + +- **Bug Fix** Amazon Q Feature Dev: display limit reached error message +- **Bug Fix** Amazon Q chat: `@workspace` command shown in all tab types +- **Bug Fix** Chat container exceeds width of container +- **Bug Fix** amazon q inline: skip indexing when no workspace folders are found +- **Bug Fix** file details and name unneccessary cropping +- **Bug Fix** Amazon Q /dev: update diff window behavior after a change is accepted +- **Feature** Amazon Q /dev: support `.gradle` files +- **Feature** Code Transform: Enable support for Java 17 projects. +- **Feature** Notifications: Support for delivering critical alerts and product updates +- **Feature** Retrieve and display a customization name when a customization is overridden in an AB test +- **Feature** Feature(Amazon Q Code Transformation): support conversions of embedded SQL from Oracle to PostgreSQL + +## 1.36.0 2024-11-14 + +- **Bug Fix** Fix broken inline suggestion auto-trigger on Systemverfilog files if users dont have systemverilog extension installed and enabled +- **Bug Fix** tutorial always showing on start +- **Feature** Enable default `@workspace` context of Amazon Q chat for certain users +- **Feature** Amazon Q /dev: Add an action to accept individual files + +## 1.35.0 2024-11-11 + +- **Breaking Change** Change focus chat keybind to win+alt+i on Windows, cmd+alt+i on macOS, and meta+alt+i on Linux +- **Bug Fix** Fix suboptimal inline suggestions from Amazon Q caused by improperly formatted supplemental context +- **Bug Fix** Fix empty chunks being sent to service and get validationException + +## 1.34.0 2024-11-07 + +- **Bug Fix** Align example help text with prompt message in chat +- **Bug Fix** Improve `@workspace` index auto pause start strategy. +- **Feature** Allow users to View and Apply diff when they explictily send code to Amazon Q using - Fix, Refactor, Optimize and Send To Prompt. +- **Feature** Security Scan: Auto-scan now supports JSX, TSX, Kotlin, Scala, and Shell files. + +## 1.33.0 2024-10-30 + +- **Bug Fix** Amazon Q /dev: fix for stop button showing for Code Transformation + +## 1.32.0 2024-10-29 + +- **Bug Fix** Remove warning when no input is provided to inline chat input box +- **Bug Fix** Use Sagemaker environment IAM Credentials for Code Completion when they're available +- **Bug Fix** Inline: Code completion not working for Sagemaker Pro Tier users. +- **Bug Fix** Disable /transform and /dev commands for sagemaker users as they're not supported +- **Feature** Amazon SageMaker Studio: Enable Free Tier Chat for IAM users + +## 1.31.0 2024-10-29 + +- **Breaking Change** Change keybind for focusing chat to ctrl+win+i on Windows, ctrl+cmd+i on macOS and ctrl+meta+i on Linux +- **Bug Fix** Inline Suggestions: Occasional `ValidationException` if user context has too many characters. +- **Bug Fix** Update `@workspace` index when adding or deleting a file +- **Bug Fix** fixed device code detection when running auth through tunneled vscode +- **Feature** Use inline chat to select code and transform it with natural language instructions +- **Feature** Amazon Q /dev: Add stop generation action +- **Feature** Provide more frequent updates about code changes made by agent + +## 1.30.0 2024-10-17 + +- **Bug Fix** Various fixes and changes + +## 1.29.0 2024-10-10 + +- **Bug Fix** Amazon Q /dev: include telemetry for workspace usage when generating new files +- **Bug Fix** Amazon Q extension may fail to start if AWS Toolkit extension fails to start +- **Bug Fix** Start language server by default +- **Bug Fix** Amazon Q Feature Dev: Add error messages when the upload URL expires +- **Bug Fix** Amazon Q (/dev): view diffs of previous /dev iterations +- **Bug Fix** Q dev handle no change required +- **Deprecation** The next release of this extension will require VS Code 1.83.0 or newer. +- **Feature** Automatically pause and resume @workspace indexing when OS CPU load is high +- **Feature** Add buttons to code blocks to view and accept diffs. +- **Feature** Inline completion for more json files, and all yaml files +- **Feature** Show a one-time warning if new VS Code is required +- **Removal** Minimum required VSCode version is now 1.83 + +## 1.28.0 2024-10-03 + +- **Bug Fix** Amazon Q /dev: define first folder as a root path for LLM-created files when using workspaces +- **Feature** Amazon Q Code Transformation: allow users to skip running tests +- **Feature** Amazon Q Developer: Updated legal disclaimer text + ## 1.27.0 2024-09-27 - **Bug Fix** Security Scan: Fixes an issue that incorrectly removes hardcoded credentials detections from auto scans. diff --git a/packages/amazonq/README.md b/packages/amazonq/README.md index beee7557b6a..46091a98d10 100644 --- a/packages/amazonq/README.md +++ b/packages/amazonq/README.md @@ -3,62 +3,60 @@ [![Youtube Channel Views](https://img.shields.io/youtube/channel/views/UCd6MoB9NC6uYN2grvUNT-Zg?style=flat-square&logo=youtube&label=Youtube)](https://www.youtube.com/@amazonwebservices) ![Marketplace Installs](https://img.shields.io/vscode-marketplace/i/AmazonWebServices.amazon-q-vscode.svg?label=Installs&style=flat-square) -# Getting Started +# Agent capabilities -**Free Tier** - create or log in with an AWS Builder ID (a personal profile from AWS). +### Implement new features -**Pro Tier** - if your organization is on the Amazon Q Developer Pro tier, log in with single sign-on. +`/dev` to task Amazon Q with generating new code across your entire project and implement features. -![Authentication gif](https://raw.githubusercontent.com/aws/aws-toolkit-vscode/HEAD/docs/marketplace/vscode/amazonq/auth-Q.gif) +### Generate documentation -# Features +`/doc` to task Amazon Q with writing API, technical design, and onboarding documentation. -## Inline code suggestions +### Automate code reviews -Code faster with inline code suggestions as you type. +`/review` to ask Amazon Q to perform code reviews, flagging suspicious code patterns and assessing deployment risk. -![Inline code suggestion demo](https://raw.githubusercontent.com/aws/aws-toolkit-vscode/HEAD/docs/marketplace/vscode/amazonq/inline.gif) +### Generate unit tests -[_15+ languages supported including Python, TypeScript, Rust, Terraform, AWS Cloudformation, and more_](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-language-ide-support.html) - -## Chat +`/test` to ask Amazon Q to generate unit tests and add them to your project, helping you improve code quality, fast. -Generate code, explain code, and get answers to questions about software development. +### Transform workloads -![Generate code using chat](https://raw.githubusercontent.com/aws/aws-toolkit-vscode/HEAD/docs/marketplace/vscode/amazonq/chat.gif) +`/transform` to upgrade your Java applications in minutes, not weeks. -## Security scans +
-Analyze and fix security vulnerabilities in your project. +# Core features -![Fix security vulnerability demo](https://raw.githubusercontent.com/aws/aws-toolkit-vscode/HEAD/docs/marketplace/vscode/amazonq/security-scan.gif) +### Inline chat -[_10 languages supported including Python, TypeScript, C#, and more_](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security-scans.html) +Seamlessly initiate chat within the inline coding experience. Select a section of code that you need assistance with and initiate chat within the editor to request actions such as "Optimize this code", "Add comments", or "Write tests". -## Agent for software development +### Chat -Amazon Q can implement new functionality across multiple files in your workspace. +Generate code, explain code, and get answers about software development. -Type “/” in chat to open the quick actions menu and choose the “/dev” action. +### Inline suggestions -![Agent for software development demo](https://raw.githubusercontent.com/aws/aws-toolkit-vscode/HEAD/docs/marketplace/vscode/amazonq/dev.gif) +Receive real-time code suggestions ranging from snippets to full functions based on your comments and existing code. -_Note - this demo has been trimmed, Amazon Q can take several minutes to generate code_ +[_15+ languages supported including Python, TypeScript, Rust, Terraform, AWS Cloudformation, and more_](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-language-ide-support.html) -## Agent for code transformation +### Code reference log -Upgrade your Java applications in minutes, not weeks. +Attribute code from Amazon Q that is similar to training data. When code suggestions similar to training data are accepted, they will be added to the code reference log. -Type “/” in chat to open the quick actions menu and choose the “/transform” action. +
-![Agent for code transformation demo](https://raw.githubusercontent.com/aws/aws-toolkit-vscode/HEAD/docs/marketplace/vscode/amazonq/transform.png) +# Getting Started -[_Currently supports upgrading Java 8 or 11 Maven projects to Java 17_](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-transformation.html#prerequisites) +**Free Tier** - create or log in with an AWS Builder ID (a personal profile from AWS). -## Code reference log +**Pro Tier** - if your organization is on the Amazon Q Developer Pro tier, log in with single sign-on. -Attribute code from Amazon Q that is similar to training data. When code suggestions similar to training data are accepted, they will be added to the code reference log. +![Authentication gif](https://raw.githubusercontent.com/aws/aws-toolkit-vscode/HEAD/docs/marketplace/vscode/amazonq/auth-Q.gif) -## Troubleshooting & feedback +# Troubleshooting & feedback [File a bug](https://github.com/aws/aws-toolkit-vscode/issues/new?assignees=&labels=bug&projects=&template=bug_report.md) or [submit a feature request](https://github.com/aws/aws-toolkit-vscode/issues/new?assignees=&labels=feature-request&projects=&template=feature_request.md) on our Github repository. diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 0f60e5c36f3..9e144c171a3 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -1,8 +1,8 @@ { "name": "amazon-q-vscode", "displayName": "Amazon Q", - "description": "Amazon Q is your generative AI-powered assistant across the software development lifecycle.", - "version": "1.28.0-SNAPSHOT", + "description": "The most capable generative AI-powered assistant for building, operating, and transforming software, with advanced capabilities for managing data and AI", + "version": "1.42.0-SNAPSHOT", "extensionKind": [ "workspace" ], @@ -46,19 +46,20 @@ "browser": "./dist/src/extensionWeb", "scripts": { "vscode:prepublish": "npm run clean && npm run buildScripts && webpack --mode production", - "buildScripts": "npm run generateNonCodeFiles && npm run copyFiles && npm run generateIcons && npm run generateSettings && npm run syncPackageJson && tsc -p ./ --noEmit", + "buildScripts": "npm run generateNonCodeFiles && npm run copyFiles && npm run generateIcons && npm run generateSettings && tsc -p ./ --noEmit", "generateNonCodeFiles": "ts-node ../../scripts/generateNonCodeFiles.ts", "copyFiles": "ts-node ./scripts/build/copyFiles.ts", - "syncPackageJson": "ts-node ./scripts/build/syncPackageJson.ts", "clean": "ts-node ../../scripts/clean.ts dist/ LICENSE NOTICE", - "compile": "npm run clean && npm run buildScripts && webpack --mode development", + "compile": "npm run clean && npm run buildScripts && webpack", + "compileDev": "npm run testCompile && webpack --mode development", "compileOnly": "tsc -p ./", "package": "ts-node ../../scripts/package.ts", "lint": "true", "watch": "npm run clean && npm run buildScripts && tsc -watch -p ./", "testCompile": "npm run clean && npm run buildScripts && npm run compileOnly", - "test": "npm run testCompile && c8 --allowExternal ts-node ../core/scripts/test/test.ts dist/test/unit/index.js ../core/dist/src/testFixtures/workspaceFolder", - "testE2E": "npm run testCompile && c8 --allowExternal ts-node ../core/scripts/test/testE2E.ts dist/test/e2e/index.js ../core/dist/src/testFixtures/workspaceFolder", + "test": "npm run testCompile && c8 --allowExternal ts-node ../core/scripts/test/launchTest.ts unit dist/test/unit/index.js ../core/dist/src/testFixtures/workspaceFolder", + "testE2E": "npm run testCompile && c8 --allowExternal ts-node ../core/scripts/test/launchTest.ts e2e dist/test/e2e/index.js ../core/dist/src/testFixtures/workspaceFolder", + "testWeb": "npm run compileDev && c8 --allowExternal ts-node ../core/scripts/test/launchTest.ts web dist/test/web/testRunnerWebCore.js", "webRun": "npx @vscode/test-web --open-devtools --browserOption=--disable-web-security --waitForDebugger=9222 --extensionDevelopmentPath=. .", "webWatch": "npm run clean && npm run buildScripts && webpack --mode development --watch", "serve": "webpack serve --config-name mainServe --mode development", @@ -70,7 +71,6 @@ "dependencies": { "aws-core-vscode": "file:../core/" }, - "devDependencies": {}, "contributesComments": { "configuration": { "properties": "Any settings also defined in packages/core/package.json will override same-named settings in this file." @@ -111,6 +111,10 @@ "type": "boolean", "default": false }, + "minIdeVersion": { + "type": "boolean", + "default": false + }, "ssoCacheError": { "type": "boolean", "default": false @@ -157,6 +161,14 @@ "markdownDescription": "%AWS.configuration.description.amazonq.workspaceIndexMaxSize%", "default": 250, "scope": "application" + }, + "amazonQ.ignoredSecurityIssues": { + "type": "array", + "markdownDescription": "%AWS.configuration.description.amazonq.ignoredSecurityIssues%", + "scope": "window", + "items": { + "type": "string" + } } } }, @@ -183,17 +195,29 @@ }, "views": { "amazonq": [ + { + "id": "aws.amazonq.notifications", + "name": "%AWS.notifications.title%", + "when": "!isCloud9 && !aws.isSageMaker && aws.amazonq.notifications.show" + }, { "type": "webview", "id": "aws.amazonq.AmazonCommonAuth", "name": "%AWS.amazonq.login%", - "when": "!aws.isSageMaker && !aws.isWebExtHost && aws.amazonq.showLoginView" + "when": "!aws.isWebExtHost && aws.amazonq.showLoginView" + }, + { + "type": "tree", + "id": "aws.amazonq.SecurityIssuesTree", + "name": "%AWS.amazonq.security%", + "when": "!aws.isSageMaker && !aws.isWebExtHost && !aws.amazonq.showLoginView", + "visibility": "collapsed" }, { "type": "webview", "id": "aws.AmazonQChatView", "name": "%AWS.amazonq.chat%", - "when": "!aws.isSageMaker && !aws.isWebExtHost && !aws.amazonq.showLoginView" + "when": "!aws.isWebExtHost && !aws.amazonq.showLoginView" }, { "id": "aws.AmazonQNeverShowBadge", @@ -232,6 +256,16 @@ "view": "aws.amazonq.transformationProposedChangesTree", "contents": "Project transformation is complete.\n Downloading the proposed changes...", "when": "gumby.reviewState == PreparingReview" + }, + { + "view": "aws.amazonq.SecurityIssuesTree", + "contents": "No code issues have been detected in the workspace.", + "when": "!aws.amazonq.security.noMatches" + }, + { + "view": "aws.amazonq.SecurityIssuesTree", + "contents": "No matches.\n[Clear Filters](command:aws.amazonq.securityIssuesTreeFilter.clearFilters)", + "when": "aws.amazonq.security.noMatches" } ], "submenus": [ @@ -246,6 +280,11 @@ { "label": "%AWS.generic.help%", "id": "aws.amazonq.submenu.help" + }, + { + "label": "%AWS.generic.moreActions%", + "id": "aws.amazonq.submenu.securityIssueMoreActions", + "icon": "$(ellipsis)" } ], "menus": { @@ -330,6 +369,38 @@ "submenu": "aws.amazonq.submenu.help", "when": "view == aws.AmazonQChatView || view == aws.amazonq.AmazonCommonAuth", "group": "y_toolkitMeta@2" + }, + { + "command": "aws.amazonq.security.showFilters", + "when": "view == aws.amazonq.SecurityIssuesTree", + "group": "navigation" + } + ], + "view/item/context": [ + { + "command": "_aws.amazonq.notifications.dismiss", + "when": "viewItem == amazonqNotificationStartUp", + "group": "inline@1" + }, + { + "command": "aws.amazonq.openSecurityIssuePanel", + "when": "view == aws.amazonq.SecurityIssuesTree && (viewItem == issueWithoutFix || viewItem == issueWithFix)", + "group": "inline@4" + }, + { + "command": "aws.amazonq.security.ignore", + "when": "view == aws.amazonq.SecurityIssuesTree && (viewItem == issueWithoutFix || viewItem == issueWithFix)", + "group": "inline@5" + }, + { + "command": "aws.amazonq.security.generateFix", + "when": "view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithoutFix", + "group": "inline@6" + }, + { + "submenu": "aws.amazonq.submenu.securityIssueMoreActions", + "when": "view == aws.amazonq.SecurityIssuesTree && (viewItem == issueWithoutFix || viewItem == issueWithFix)", + "group": "inline@7" } ], "amazonqEditorContextSubmenu": [ @@ -351,12 +422,15 @@ }, { "command": "aws.amazonq.generateUnitTests", - "group": "cw_chat@5", - "when": "false" + "group": "cw_chat@5" }, { "command": "aws.amazonq.sendToPrompt", "group": "cw_chat@6" + }, + { + "command": "aws.amazonq.inline.invokeChat", + "group": "cw_chat@7" } ], "editor/context": [ @@ -377,6 +451,14 @@ } ], "aws.amazonq.submenu.help": [ + { + "command": "aws.amazonq.walkthrough.show", + "group": "1_help@1" + }, + { + "command": "aws.amazonq.exploreAgents", + "group": "1_help@2" + }, { "command": "aws.amazonq.github", "group": "1_help@3" @@ -389,9 +471,36 @@ "command": "aws.amazonq.viewLogs", "group": "1_help@5" } + ], + "aws.amazonq.submenu.securityIssueMoreActions": [ + { + "command": "aws.amazonq.security.explain", + "group": "1_more@1" + }, + { + "command": "aws.amazonq.applySecurityFix", + "when": "view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithFix", + "group": "1_more@3" + }, + { + "command": "aws.amazonq.security.regenerateFix", + "when": "view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithFix", + "group": "1_more@4" + }, + { + "command": "aws.amazonq.security.ignoreAll", + "group": "1_more@5" + } ] }, "commands": [ + { + "command": "_aws.amazonq.notifications.dismiss", + "title": "%AWS.generic.dismiss%", + "category": "%AWS.amazonq.title%", + "enablement": "isCloud9 || !aws.isWebExtHost", + "icon": "$(remove-close)" + }, { "command": "aws.amazonq.explainCode", "title": "%AWS.command.amazonq.explainCode%", @@ -399,7 +508,7 @@ "enablement": "aws.codewhisperer.connected" }, { - "command": "aws.amazonq.security.scan", + "command": "aws.amazonq.security.scan-statusbar", "title": "%AWS.command.amazonq.security.scan%", "category": "%AWS.amazonq.title%", "enablement": "aws.codewhisperer.connected" @@ -432,7 +541,7 @@ "command": "aws.amazonq.generateUnitTests", "title": "%AWS.command.amazonq.generateUnitTests%", "category": "%AWS.amazonq.title%", - "enablement": "false" + "enablement": "aws.codewhisperer.connected" }, { "command": "aws.amazonq.reconnect", @@ -571,14 +680,81 @@ "title": "%AWS.amazonq.toggleCodeScan%", "category": "%AWS.amazonq.title%", "enablement": "aws.codewhisperer.connected" + }, + { + "command": "aws.amazonq.securityIssuesTreeFilter.clearFilters", + "title": "Clear Filters", + "enablement": "view == aws.amazonq.SecurityIssuesTree" + }, + { + "command": "aws.amazonq.security.generateFix", + "title": "%AWS.command.amazonq.generateFix%", + "icon": "$(wrench)", + "enablement": "view == aws.amazonq.SecurityIssuesTree" + }, + { + "command": "aws.amazonq.applySecurityFix", + "title": "%AWS.command.amazonq.acceptFix%", + "icon": "$(check)", + "enablement": "view == aws.amazonq.SecurityIssuesTree" + }, + { + "command": "aws.amazonq.security.regenerateFix", + "title": "%AWS.command.amazonq.regenerateFix%", + "icon": "$(lightbulb-autofix)", + "enablement": "view == aws.amazonq.SecurityIssuesTree" + }, + { + "command": "aws.amazonq.openSecurityIssuePanel", + "title": "%AWS.command.amazonq.viewDetails%", + "icon": "$(search)", + "enablement": "view == aws.amazonq.SecurityIssuesTree" + }, + { + "command": "aws.amazonq.security.explain", + "title": "%AWS.command.amazonq.explainIssue%", + "enablement": "view == aws.amazonq.SecurityIssuesTree" + }, + { + "command": "aws.amazonq.security.ignore", + "title": "%AWS.command.amazonq.ignoreIssue%", + "icon": "$(circle-slash)", + "enablement": "view == aws.amazonq.SecurityIssuesTree" + }, + { + "command": "aws.amazonq.security.ignoreAll", + "title": "%AWS.command.amazonq.ignoreAllIssues%", + "enablement": "view == aws.amazonq.SecurityIssuesTree" + }, + { + "command": "aws.amazonq.security.showFilters", + "title": "%AWS.command.amazonq.filterIssues%", + "icon": "$(list-filter)", + "enablement": "view == aws.amazonq.SecurityIssuesTree" + }, + { + "command": "aws.amazonq.inline.invokeChat", + "title": "%AWS.amazonq.inline.invokeChat%", + "category": "%AWS.amazonq.title%", + "enablement": "aws.codewhisperer.connected" + }, + { + "command": "aws.amazonq.exploreAgents", + "title": "%AWS.amazonq.exploreAgents%", + "category": "%AWS.amazonq.title%", + "enablement": "aws.codewhisperer.connected" + }, + { + "command": "aws.amazonq.walkthrough.show", + "title": "%AWS.amazonq.welcomeWalkthrough%" } ], "keybindings": [ { "command": "_aws.amazonq.focusChat.keybinding", - "win": "win+i", - "mac": "cmd+i", - "linux": "meta+i" + "win": "win+alt+i", + "mac": "cmd+alt+i", + "linux": "meta+alt+i" }, { "command": "aws.amazonq.explainCode", @@ -614,8 +790,7 @@ "command": "aws.amazonq.generateUnitTests", "key": "win+alt+t", "mac": "cmd+alt+t", - "linux": "meta+alt+t", - "when": "false" + "linux": "meta+alt+t" }, { "command": "aws.amazonq.invokeInlineCompletion", @@ -644,6 +819,23 @@ "key": "left", "command": "editor.action.inlineSuggest.showPrevious", "when": "inlineSuggestionVisible && !editorReadonly && aws.codewhisperer.connected" + }, + { + "command": "aws.amazonq.inline.invokeChat", + "win": "ctrl+i", + "mac": "cmd+i", + "linux": "ctrl+i", + "when": "editorTextFocus && aws.codewhisperer.connected" + }, + { + "command": "aws.amazonq.inline.waitForUserDecisionAcceptAll", + "key": "enter", + "when": "editorTextFocus && aws.codewhisperer.connected && amazonq.inline.codelensShortcutEnabled" + }, + { + "command": "aws.amazonq.inline.waitForUserDecisionRejectAll", + "key": "escape", + "when": "editorTextFocus && aws.codewhisperer.connected && amazonq.inline.codelensShortcutEnabled" } ], "icons": { @@ -668,327 +860,362 @@ "fontCharacter": "\\f1ac" } }, - "aws-amazonq-transform-arrow-dark": { + "aws-amazonq-severity-critical": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ad" } }, - "aws-amazonq-transform-arrow-light": { + "aws-amazonq-severity-high": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ae" } }, - "aws-amazonq-transform-default-dark": { + "aws-amazonq-severity-info": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1af" } }, - "aws-amazonq-transform-default-light": { + "aws-amazonq-severity-low": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b0" } }, - "aws-amazonq-transform-dependencies-dark": { + "aws-amazonq-severity-medium": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b1" } }, - "aws-amazonq-transform-dependencies-light": { + "aws-amazonq-transform-arrow-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b2" } }, - "aws-amazonq-transform-file-dark": { + "aws-amazonq-transform-arrow-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b3" } }, - "aws-amazonq-transform-file-light": { + "aws-amazonq-transform-default-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b4" } }, - "aws-amazonq-transform-logo": { + "aws-amazonq-transform-default-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b5" } }, - "aws-amazonq-transform-step-into-dark": { + "aws-amazonq-transform-dependencies-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b6" } }, - "aws-amazonq-transform-step-into-light": { + "aws-amazonq-transform-dependencies-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b7" } }, - "aws-amazonq-transform-variables-dark": { + "aws-amazonq-transform-file-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b8" } }, - "aws-amazonq-transform-variables-light": { + "aws-amazonq-transform-file-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1b9" } }, - "aws-applicationcomposer-icon": { + "aws-amazonq-transform-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ba" } }, - "aws-applicationcomposer-icon-dark": { + "aws-amazonq-transform-step-into-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1bb" } }, - "aws-apprunner-service": { + "aws-amazonq-transform-step-into-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1bc" } }, - "aws-cdk-logo": { + "aws-amazonq-transform-variables-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1bd" } }, - "aws-cloudformation-stack": { + "aws-amazonq-transform-variables-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1be" } }, - "aws-cloudwatch-log-group": { + "aws-applicationcomposer-icon": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1bf" } }, - "aws-codecatalyst-logo": { + "aws-applicationcomposer-icon-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c0" } }, - "aws-codewhisperer-icon-black": { + "aws-apprunner-service": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c1" } }, - "aws-codewhisperer-icon-white": { + "aws-cdk-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c2" } }, - "aws-codewhisperer-learn": { + "aws-cloudformation-stack": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c3" } }, - "aws-ecr-registry": { + "aws-cloudwatch-log-group": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c4" } }, - "aws-ecs-cluster": { + "aws-codecatalyst-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c5" } }, - "aws-ecs-container": { + "aws-codewhisperer-icon-black": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c6" } }, - "aws-ecs-service": { + "aws-codewhisperer-icon-white": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c7" } }, - "aws-generic-attach-file": { + "aws-codewhisperer-learn": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c8" } }, - "aws-iot-certificate": { + "aws-ecr-registry": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1c9" } }, - "aws-iot-policy": { + "aws-ecs-cluster": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ca" } }, - "aws-iot-thing": { + "aws-ecs-container": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1cb" } }, - "aws-lambda-function": { + "aws-ecs-service": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1cc" } }, - "aws-mynah-MynahIconBlack": { + "aws-generic-attach-file": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1cd" } }, - "aws-mynah-MynahIconWhite": { + "aws-iot-certificate": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1ce" } }, - "aws-mynah-logo": { + "aws-iot-policy": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1cf" } }, - "aws-redshift-cluster": { + "aws-iot-thing": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d0" } }, - "aws-redshift-cluster-connected": { + "aws-lambda-function": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d1" } }, - "aws-redshift-database": { + "aws-mynah-MynahIconBlack": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d2" } }, - "aws-redshift-redshift-cluster-connected": { + "aws-mynah-MynahIconWhite": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d3" } }, - "aws-redshift-schema": { + "aws-mynah-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d4" } }, - "aws-redshift-table": { + "aws-redshift-cluster": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d5" } }, - "aws-s3-bucket": { + "aws-redshift-cluster-connected": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d6" } }, - "aws-s3-create-bucket": { + "aws-redshift-database": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d7" } }, - "aws-schemas-registry": { + "aws-redshift-redshift-cluster-connected": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d8" } }, - "aws-schemas-schema": { + "aws-redshift-schema": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1d9" } }, - "aws-stepfunctions-preview": { + "aws-redshift-table": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1da" } + }, + "aws-s3-bucket": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1db" + } + }, + "aws-s3-create-bucket": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1dc" + } + }, + "aws-schemas-registry": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1dd" + } + }, + "aws-schemas-schema": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1de" + } + }, + "aws-stepfunctions-preview": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1df" + } } }, "walkthroughs": [ @@ -1045,6 +1272,6 @@ }, "engines": { "npm": "^10.1.0", - "vscode": "^1.68.0" + "vscode": "^1.83.0" } } diff --git a/packages/amazonq/scripts/build/copyFiles.ts b/packages/amazonq/scripts/build/copyFiles.ts index 75ee0540e92..725c66ad7c0 100644 --- a/packages/amazonq/scripts/build/copyFiles.ts +++ b/packages/amazonq/scripts/build/copyFiles.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as fs from 'fs-extra' +/* eslint-disable no-restricted-imports */ +import fs from 'fs' import * as path from 'path' // Moves all dependencies into `dist` @@ -72,14 +73,14 @@ const tasks: CopyTask[] = [ }, ] -async function copy(task: CopyTask): Promise { +function copy(task: CopyTask): void { const src = path.resolve(projectRoot, task.target) const dst = path.resolve(outRoot, task.destination ?? task.target) try { - await fs.copy(src, dst, { + fs.cpSync(src, dst, { recursive: true, - overwrite: true, + force: true, errorOnExist: false, }) } catch (error) { @@ -87,18 +88,20 @@ async function copy(task: CopyTask): Promise { } } -void (async () => { - const args = process.argv.slice(2) - if (args.includes('--vueHr')) { - vueHr = true - console.log('Using Vue Hot Reload webpacks from core/') - } +const args = process.argv.slice(2) +if (args.includes('--vueHr')) { + vueHr = true + console.log('Using Vue Hot Reload webpacks from core/') +} +function main() { try { - await Promise.all(tasks.map(copy)) + tasks.map(copy) } catch (error) { console.error('`copyFiles.ts` failed') console.error(error) process.exit(1) } -})() +} + +void main() diff --git a/packages/amazonq/scripts/build/syncPackageJson.ts b/packages/amazonq/scripts/build/syncPackageJson.ts deleted file mode 100644 index 654078fbdb2..00000000000 --- a/packages/amazonq/scripts/build/syncPackageJson.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * We are currently in the process of splitting the toolkit into a core library and separate extensions. - * A lot of the core toolkit code depends on contents of its package.json. However, in order for - * individual extensions to function, they need to have the same entries in their local package.jsons as well. - * - * Unlike the Toolkit extension, the Amazon Q extension only needs to copy its settings from the core. - * Settings type checking is performed at compile time in core/src/shared/settings.ts, so it needs to - * exist in the core package.json as well. - * - * TODO: Drop settings initialization into respective extensions. - */ - -import * as fs from 'fs-extra' - -function main() { - const packageJsonFile = './package.json' - const coreLibPackageJsonFile = '../core/package.json' - - const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, { encoding: 'utf-8' })) - const coreLibPackageJson = JSON.parse(fs.readFileSync(coreLibPackageJsonFile, { encoding: 'utf-8' })) - - const coreSettings = coreLibPackageJson.contributes.configuration.properties - Object.keys(coreSettings).forEach((key) => { - if (key.startsWith('amazonQ')) { - packageJson.contributes.configuration.properties[key] = coreSettings[key] - } - }) - - fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, undefined, ' ') + '\n') -} - -main() diff --git a/packages/amazonq/src/api.ts b/packages/amazonq/src/api.ts index 51b5225079b..03b2a32ea55 100644 --- a/packages/amazonq/src/api.ts +++ b/packages/amazonq/src/api.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { SendMessageCommandOutput, SendMessageRequest } from '@amzn/amazon-q-developer-streaming-client' import { GenerateAssistantResponseCommandOutput, GenerateAssistantResponseRequest } from '@amzn/codewhisperer-streaming' import { AuthUtil } from 'aws-core-vscode/codewhisperer' import { ChatSession } from 'aws-core-vscode/codewhispererChat' @@ -12,7 +13,11 @@ export default { chatApi: { async chat(request: GenerateAssistantResponseRequest): Promise { const chatSession = new ChatSession() - return chatSession.chat(request) + return chatSession.chatSso(request) + }, + async chatIam(request: SendMessageRequest): Promise { + const chatSession = new ChatSession() + return chatSession.chatIam(request) }, }, authApi: { diff --git a/packages/amazonq/src/app/amazonqScan/app.ts b/packages/amazonq/src/app/amazonqScan/app.ts new file mode 100644 index 00000000000..d639ab6bf2a --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/app.ts @@ -0,0 +1,87 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { + AmazonQAppInitContext, + MessagePublisher, + MessageListener, + focusAmazonQPanel, + DefaultAmazonQAppInitContext, +} from 'aws-core-vscode/amazonq' +import { AuthUtil, codeScanState, onDemandFileScanState } from 'aws-core-vscode/codewhisperer' +import { ScanChatControllerEventEmitters, ChatSessionManager } from 'aws-core-vscode/amazonqScan' +import { ScanController } from './chat/controller/controller' +import { AppToWebViewMessageDispatcher } from './chat/views/connector/connector' +import { Messenger } from './chat/controller/messenger/messenger' +import { UIMessageListener } from './chat/views/actions/uiMessageListener' +import { debounce } from 'lodash' +import { Commands, placeholder } from 'aws-core-vscode/shared' + +export function init(appContext: AmazonQAppInitContext) { + const scanChatControllerEventEmitters: ScanChatControllerEventEmitters = { + authClicked: new vscode.EventEmitter(), + tabOpened: new vscode.EventEmitter(), + tabClosed: new vscode.EventEmitter(), + runScan: new vscode.EventEmitter(), + formActionClicked: new vscode.EventEmitter(), + errorThrown: new vscode.EventEmitter(), + showSecurityScan: new vscode.EventEmitter(), + scanStopped: new vscode.EventEmitter(), + followUpClicked: new vscode.EventEmitter(), + scanProgress: new vscode.EventEmitter(), + processResponseBodyLinkClick: new vscode.EventEmitter(), + fileClicked: new vscode.EventEmitter(), + scanCancelled: new vscode.EventEmitter(), + } + const dispatcher = new AppToWebViewMessageDispatcher(appContext.getAppsToWebViewMessagePublisher()) + const messenger = new Messenger(dispatcher) + + new ScanController(scanChatControllerEventEmitters, messenger, appContext.onDidChangeAmazonQVisibility.event) + + const scanChatUIInputEventEmitter = new vscode.EventEmitter() + + new UIMessageListener({ + chatControllerEventEmitters: scanChatControllerEventEmitters, + webViewMessageListener: new MessageListener(scanChatUIInputEventEmitter), + }) + + appContext.registerWebViewToAppMessagePublisher(new MessagePublisher(scanChatUIInputEventEmitter), 'review') + + const debouncedEvent = debounce(async () => { + const authenticated = (await AuthUtil.instance.getChatAuthState()).amazonQ === 'connected' + let authenticatingSessionID = '' + + if (authenticated) { + const session = ChatSessionManager.Instance.getSession() + + if (session.isTabOpen() && session.isAuthenticating) { + authenticatingSessionID = session.tabID! + session.isAuthenticating = false + } + } + + messenger.sendAuthenticationUpdate(authenticated, [authenticatingSessionID]) + }, 500) + + AuthUtil.instance.secondaryAuth.onDidChangeActiveConnection(() => { + return debouncedEvent() + }) + + Commands.register('aws.amazonq.security.scan-statusbar', async () => { + if (AuthUtil.instance.isConnectionExpired()) { + await AuthUtil.instance.notifyReauthenticate() + } + return focusAmazonQPanel.execute(placeholder, 'amazonq.security.scan').then(() => { + DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().publish({ + sender: 'amazonqCore', + command: 'review', + }) + }) + }) + + codeScanState.setChatControllers(scanChatControllerEventEmitters) + onDemandFileScanState.setChatControllers(scanChatControllerEventEmitters) +} diff --git a/packages/amazonq/src/app/amazonqScan/chat/controller/controller.ts b/packages/amazonq/src/app/amazonqScan/chat/controller/controller.ts new file mode 100644 index 00000000000..599271f0f3b --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/chat/controller/controller.ts @@ -0,0 +1,358 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * This class is responsible for responding to UI events by calling + * the Scan extension. + */ +import * as vscode from 'vscode' +import { AuthController } from 'aws-core-vscode/amazonq' +import { getLogger, placeholder, i18n, openUrl, fs, TabTypeDataMap, randomUUID } from 'aws-core-vscode/shared' +import { ScanChatControllerEventEmitters, Session, ChatSessionManager } from 'aws-core-vscode/amazonqScan' +import { + AggregatedCodeScanIssue, + AuthUtil, + CodeAnalysisScope, + codeScanState, + isGitRepo, + onDemandFileScanState, + SecurityScanError, + SecurityScanStep, + showFileScan, + showSecurityScan, +} from 'aws-core-vscode/codewhisperer' +import { Messenger, ScanNamedMessages } from './messenger/messenger' +import MessengerUtils from './messenger/messengerUtils' +import { + cancellingProgressField, + fileScanProgressField, + projectScanProgressField, + ScanAction, + scanProgressMessage, + scanSummaryMessage, +} from '../../models/constants' +import path from 'path' + +export class ScanController { + private readonly messenger: Messenger + private readonly sessionStorage: ChatSessionManager + private authController: AuthController + + public constructor( + private readonly chatControllerMessageListeners: ScanChatControllerEventEmitters, + messenger: Messenger, + onDidChangeAmazonQVisibility: vscode.Event + ) { + this.messenger = messenger + this.sessionStorage = ChatSessionManager.Instance + this.authController = new AuthController() + + this.chatControllerMessageListeners.tabOpened.event((data) => { + return this.tabOpened(data) + }) + + this.chatControllerMessageListeners.tabClosed.event((data) => { + return this.tabClosed(data) + }) + + this.chatControllerMessageListeners.authClicked.event((data) => { + this.authClicked(data) + }) + + this.chatControllerMessageListeners.runScan.event((data) => { + return this.scanInitiated(data) + }) + + this.chatControllerMessageListeners.formActionClicked.event((data) => { + return this.formActionClicked(data) + }) + + this.chatControllerMessageListeners.errorThrown.event((data) => { + return this.handleError(data) + }) + + this.chatControllerMessageListeners.showSecurityScan.event((data) => { + return this.handleScanResults(data) + }) + + this.chatControllerMessageListeners.scanStopped.event((data) => { + return this.handleScanStopped(data) + }) + + this.chatControllerMessageListeners.followUpClicked.event((data) => { + return this.handleFollowUpClicked(data) + }) + + this.chatControllerMessageListeners.scanProgress.event((data) => { + return this.handleScanProgress(data) + }) + + this.chatControllerMessageListeners.processResponseBodyLinkClick.event((data) => { + return this.processLink(data) + }) + + this.chatControllerMessageListeners.fileClicked.event((data) => { + return this.processFileClick(data) + }) + + this.chatControllerMessageListeners.scanCancelled.event((data) => { + return this.handleScanCancelled(data) + }) + } + + private async tabOpened(message: any) { + const session: Session = this.sessionStorage.getSession() + const tabID = this.sessionStorage.setActiveTab(message.tabID) + + // check if authentication has expired + try { + getLogger().debug(`Q - Review: Session created with id: ${session.tabID}`) + + const authState = await AuthUtil.instance.getChatAuthState() + if (authState.amazonQ !== 'connected') { + void this.messenger.sendAuthNeededExceptionMessage(authState, tabID) + session.isAuthenticating = true + return + } + } catch (err: any) { + this.messenger.sendErrorMessage(err.message, message.tabID) + } + } + + private async tabClosed(data: any) { + this.sessionStorage.removeActiveTab() + } + + private authClicked(message: any) { + this.authController.handleAuth(message.authType) + + this.messenger.sendAnswer({ + type: 'answer', + tabID: message.tabID, + message: 'Follow instructions to re-authenticate ...', + }) + + // Explicitly ensure the user goes through the re-authenticate flow + this.messenger.sendChatInputEnabled(message.tabID, false) + } + + private async scanInitiated(message: any) { + const session: Session = this.sessionStorage.getSession() + try { + // check that a project is open + const workspaceFolders = vscode.workspace.workspaceFolders + if (workspaceFolders === undefined || workspaceFolders.length === 0) { + this.messenger.sendChatInputEnabled(message.tabID, false) + this.messenger.sendErrorResponse('no-project-found', message.tabID) + return + } + // check that the session is authenticated + const authState = await AuthUtil.instance.getChatAuthState() + if (authState.amazonQ !== 'connected') { + void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID) + session.isAuthenticating = true + return + } + this.messenger.sendPromptMessage({ + tabID: message.tabID, + message: i18n('AWS.amazonq.scans.runCodeScan'), + }) + this.messenger.sendCapabilityCard({ tabID: message.tabID }) + // Displaying types of scans and wait for user input + this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.scans.waitingForInput')) + + this.messenger.sendScans(message.tabID, i18n('AWS.amazonq.scans.chooseScan.description')) + } catch (e: any) { + this.messenger.sendErrorMessage(e.message, message.tabID) + } + } + + private async formActionClicked(message: any) { + const typedAction = MessengerUtils.stringToEnumValue(ScanAction, message.action as any) + switch (typedAction) { + case ScanAction.STOP_PROJECT_SCAN: + codeScanState.setToCancelling() + this.messenger.sendUpdatePromptProgress(message.tabID, cancellingProgressField) + break + case ScanAction.STOP_FILE_SCAN: + onDemandFileScanState.setToCancelling() + this.messenger.sendUpdatePromptProgress(message.tabID, cancellingProgressField) + break + } + } + + private async handleError(message: { + error: SecurityScanError + tabID: string + scope: CodeAnalysisScope + fileName: string | undefined + scanUuid?: string + }) { + if (this.isNotMatchingId(message)) { + return + } + if (message.error.code === 'NoSourceFilesError') { + this.messenger.sendScanResults(message.tabID, message.scope, message.fileName, true) + this.messenger.sendAnswer({ + tabID: message.tabID, + type: 'answer', + canBeVoted: true, + message: scanSummaryMessage(message.scope, []), + }) + } else { + this.messenger.sendErrorResponse(message.error, message.tabID) + } + } + + private async handleScanResults(message: { + error: Error + totalIssues: number + tabID: string + securityRecommendationCollection: AggregatedCodeScanIssue[] + scope: CodeAnalysisScope + fileName: string + scanUuid?: string + }) { + if (this.isNotMatchingId(message)) { + return + } + this.messenger.sendScanResults(message.tabID, message.scope, message.fileName, true) + this.messenger.sendAnswer({ + tabID: message.tabID, + type: 'answer', + canBeVoted: true, + message: scanSummaryMessage(message.scope, message.securityRecommendationCollection), + }) + } + + private async handleScanStopped(message: { tabID: string }) { + this.messenger.sendUpdatePlaceholder(message.tabID, TabTypeDataMap.review.placeholder) + // eslint-disable-next-line unicorn/no-null + this.messenger.sendUpdatePromptProgress(message.tabID, null) + this.messenger.sendChatInputEnabled(message.tabID, true) + } + + private async handleFollowUpClicked(message: any) { + switch (message.followUp.type) { + case ScanAction.RUN_PROJECT_SCAN: { + this.messenger.sendPromptMessage({ + tabID: message.tabID, + message: i18n('AWS.amazonq.scans.projectScan'), + }) + + const workspaceFolders = vscode.workspace.workspaceFolders ?? [] + for (const folder of workspaceFolders) { + if (!(await isGitRepo(folder.uri))) { + this.messenger.sendAnswer({ + tabID: message.tabID, + type: 'answer', + message: i18n('AWS.amazonq.scans.noGitRepo'), + }) + break + } + } + + this.messenger.sendScanInProgress({ + type: 'answer-stream', + tabID: message.tabID, + canBeVoted: true, + message: scanProgressMessage(0, CodeAnalysisScope.PROJECT), + }) + this.messenger.sendUpdatePromptProgress(message.tabID, projectScanProgressField) + const scanUuid = randomUUID() + this.sessionStorage.getSession().scanUuid = scanUuid + void showSecurityScan.execute(placeholder, 'amazonQChat', true, scanUuid) + break + } + case ScanAction.RUN_FILE_SCAN: { + // check if IDE has active file open. + const activeEditor = vscode.window.activeTextEditor + // also check all open editors and allow this to proceed if only one is open (even if not main focus) + const allVisibleEditors = vscode.window.visibleTextEditors + const openFileEditors = allVisibleEditors.filter((editor) => editor.document.uri.scheme === 'file') + const hasOnlyOneOpenFileSplitView = openFileEditors.length === 1 + getLogger().debug(`hasOnlyOneOpenSplitView: ${hasOnlyOneOpenFileSplitView}`) + // is not a file if the currently highlighted window is not a file, and there is either more than one or no file windows open + const isNotFile = activeEditor?.document.uri.scheme !== 'file' && !hasOnlyOneOpenFileSplitView + getLogger().debug(`activeEditor: ${activeEditor}, isNotFile: ${isNotFile}`) + if (!activeEditor || isNotFile) { + this.messenger.sendErrorResponse( + isNotFile ? 'invalid-file-type' : 'no-open-file-found', + message.tabID + ) + this.messenger.sendUpdatePlaceholder( + message.tabID, + 'Please open and highlight a source code file in order run a code scan.' + ) + this.messenger.sendChatInputEnabled(message.tabID, true) + return + } + const fileEditorToTest = hasOnlyOneOpenFileSplitView ? openFileEditors[0] : activeEditor + const fileName = fileEditorToTest.document.uri.fsPath + + this.messenger.sendPromptMessage({ + tabID: message.tabID, + message: i18n('AWS.amazonq.scans.fileScan'), + }) + this.messenger.sendScanInProgress({ + type: 'answer-stream', + tabID: message.tabID, + canBeVoted: true, + message: scanProgressMessage( + SecurityScanStep.GENERATE_ZIP - 1, + CodeAnalysisScope.FILE_ON_DEMAND, + fileName ? path.basename(fileName) : undefined + ), + }) + this.messenger.sendUpdatePromptProgress(message.tabID, fileScanProgressField) + const scanUuid = randomUUID() + this.sessionStorage.getSession().scanUuid = scanUuid + void showFileScan.execute(placeholder, 'amazonQChat', scanUuid) + break + } + } + } + + private async handleScanProgress(message: any) { + if (this.isNotMatchingId(message)) { + return + } + this.messenger.sendAnswer({ + type: 'answer-part', + tabID: message.tabID, + messageID: ScanNamedMessages.SCAN_SUBMISSION_STATUS_MESSAGE, + message: scanProgressMessage( + message.step, + message.scope, + message.fileName ? path.basename(message.fileName) : undefined + ), + }) + } + + private processLink(message: any) { + void openUrl(vscode.Uri.parse(message.link)) + } + + private async processFileClick(message: any) { + const workspaceFolders = vscode.workspace.workspaceFolders ?? [] + for (const workspaceFolder of workspaceFolders) { + const projectPath = workspaceFolder.uri.fsPath + const filePathWithoutProjectName = message.filePath.split('/').slice(1).join('/') + const absolutePath = path.join(projectPath, filePathWithoutProjectName) + if (await fs.existsFile(absolutePath)) { + const document = await vscode.workspace.openTextDocument(absolutePath) + await vscode.window.showTextDocument(document) + } + } + } + + private async handleScanCancelled(message: any) { + this.messenger.sendAnswer({ type: 'answer', tabID: message.tabID, message: 'Cancelled' }) + } + + private isNotMatchingId(data: { scanUuid?: string }): boolean { + const messagescanUuid = data.scanUuid + const currentscanUuid = this.sessionStorage.getSession().scanUuid + return Boolean(messagescanUuid) && Boolean(currentscanUuid) && messagescanUuid !== currentscanUuid + } +} diff --git a/packages/amazonq/src/app/amazonqScan/chat/controller/messenger/messenger.ts b/packages/amazonq/src/app/amazonqScan/chat/controller/messenger/messenger.ts new file mode 100644 index 00000000000..18b05e8bb84 --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/chat/controller/messenger/messenger.ts @@ -0,0 +1,230 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * This class controls the presentation of the various chat bubbles presented by the + * Q Security Scans. + * + * As much as possible, all strings used in the experience should originate here. + */ + +import { AuthFollowUpType, AuthMessageDataMap } from 'aws-core-vscode/amazonq' +import { + FeatureAuthState, + SecurityScanError, + CodeWhispererConstants, + SecurityScanStep, + DefaultCodeScanErrorMessage, +} from 'aws-core-vscode/codewhisperer' +import { ChatItemButton, ProgressField } from '@aws/mynah-ui/dist/static' +import { MynahIcons, ChatItemAction } from '@aws/mynah-ui' +import { ChatItemType } from 'aws-core-vscode/amazonq' +import { + AppToWebViewMessageDispatcher, + AuthNeededException, + AuthenticationUpdateMessage, + CapabilityCardMessage, + ChatInputEnabledMessage, + ChatMessage, + ChatPrompt, + ErrorMessage, + UpdatePlaceholderMessage, + UpdatePromptProgressMessage, +} from '../../views/connector/connector' +import { i18n } from 'aws-core-vscode/shared' +import { ScanAction, scanProgressMessage } from '../../../models/constants' +import path from 'path' + +export type UnrecoverableErrorType = 'no-project-found' | 'no-open-file-found' | 'invalid-file-type' + +export enum ScanNamedMessages { + SCAN_SUBMISSION_STATUS_MESSAGE = 'scanSubmissionMessage', +} + +export class Messenger { + public constructor(private readonly dispatcher: AppToWebViewMessageDispatcher) {} + + public sendAnswer(params: { + message?: string + type: ChatItemType + tabID: string + messageID?: string + followUps?: ChatItemAction[] + canBeVoted?: boolean + }) { + this.dispatcher.sendChatMessage( + new ChatMessage( + { + message: params.message, + messageType: params.type, + messageId: params.messageID, + followUps: params.followUps, + canBeVoted: true, + }, + params.tabID + ) + ) + } + + public sendChatInputEnabled(tabID: string, enabled: boolean) { + this.dispatcher.sendChatInputEnabled(new ChatInputEnabledMessage(tabID, enabled)) + } + + public sendUpdatePlaceholder(tabID: string, newPlaceholder: string) { + this.dispatcher.sendUpdatePlaceholder(new UpdatePlaceholderMessage(tabID, newPlaceholder)) + } + + public sendUpdatePromptProgress(tabID: string, progressField: ProgressField | null) { + this.dispatcher.sendUpdatePromptProgress(new UpdatePromptProgressMessage(tabID, progressField)) + } + + public async sendAuthNeededExceptionMessage(credentialState: FeatureAuthState, tabID: string) { + let authType: AuthFollowUpType = 'full-auth' + let message = AuthMessageDataMap[authType].message + + switch (credentialState.amazonQ) { + case 'disconnected': + authType = 'full-auth' + message = AuthMessageDataMap[authType].message + break + case 'unsupported': + authType = 'use-supported-auth' + message = AuthMessageDataMap[authType].message + break + case 'expired': + authType = 're-auth' + message = AuthMessageDataMap[authType].message + break + } + + this.dispatcher.sendAuthNeededExceptionMessage(new AuthNeededException(message, authType, tabID)) + } + + public sendAuthenticationUpdate(scanEnabled: boolean, authenticatingTabIDs: string[]) { + this.dispatcher.sendAuthenticationUpdate(new AuthenticationUpdateMessage(scanEnabled, authenticatingTabIDs)) + } + + public sendScanInProgress(params: { + message?: string + type: ChatItemType + tabID: string + messageID?: string + canBeVoted?: boolean + }) { + this.dispatcher.sendChatMessage( + new ChatMessage( + { + message: params.message, + messageType: params.type, + messageId: ScanNamedMessages.SCAN_SUBMISSION_STATUS_MESSAGE, + canBeVoted: params.canBeVoted, + }, + params.tabID + ) + ) + } + + public sendErrorMessage(errorMessage: string, tabID: string) { + this.dispatcher.sendErrorMessage( + new ErrorMessage(CodeWhispererConstants.genericErrorMessage, errorMessage, tabID) + ) + } + + public sendScanResults( + tabID: string, + scope: CodeWhispererConstants.CodeAnalysisScope, + fileName?: string, + canBeVoted?: boolean + ) { + this.dispatcher.sendChatMessage( + new ChatMessage( + { + message: scanProgressMessage( + SecurityScanStep.PROCESS_SCAN_RESULTS + 1, + scope, + fileName ? path.basename(fileName) : undefined + ), + messageType: 'answer-part', + messageId: ScanNamedMessages.SCAN_SUBMISSION_STATUS_MESSAGE, + canBeVoted: canBeVoted, + }, + tabID + ) + ) + } + + public sendErrorResponse(error: UnrecoverableErrorType | SecurityScanError, tabID: string) { + let message = DefaultCodeScanErrorMessage + const buttons: ChatItemButton[] = [] + if (typeof error === 'string') { + switch (error) { + case 'no-project-found': { + // TODO: If required we can add "Open the Projects" button in the chat panel. + message = CodeWhispererConstants.noOpenProjectsFound + break + } + case 'no-open-file-found': { + message = CodeWhispererConstants.noOpenFileFound + break + } + case 'invalid-file-type': { + message = CodeWhispererConstants.invalidFileTypeChatMessage + break + } + } + } else if (error.code === 'NoActiveFileError') { + message = CodeWhispererConstants.noOpenFileFound + } else if (error.code === 'ContentLengthError') { + message = CodeWhispererConstants.ProjectSizeExceededErrorMessage + } else if (error.code === 'NoSourceFilesError') { + message = CodeWhispererConstants.noSourceFilesErrorMessage + } else { + message = error.customerFacingMessage + } + this.dispatcher.sendChatMessage( + new ChatMessage( + { + message, + messageType: 'answer', + buttons, + }, + tabID + ) + ) + } + + public sendScans(tabID: string, message: string) { + const followUps: ChatItemAction[] = [] + followUps.push({ + pillText: i18n('AWS.amazonq.scans.projectScan'), + status: 'info', + icon: 'folder' as MynahIcons, + type: ScanAction.RUN_PROJECT_SCAN, + }) + followUps.push({ + pillText: i18n('AWS.amazonq.scans.fileScan'), + status: 'info', + icon: 'file' as MynahIcons, + type: ScanAction.RUN_FILE_SCAN, + }) + this.dispatcher.sendChatMessage( + new ChatMessage( + { + message, + messageType: 'ai-prompt', + followUps, + }, + tabID + ) + ) + } + + // This function shows selected scan type in the chat panel as a user input + public sendPromptMessage(params: { tabID: string; message: string }) { + this.dispatcher.sendPromptMessage(new ChatPrompt(params.message, params.tabID)) + } + + public sendCapabilityCard(params: { tabID: string }) { + this.dispatcher.sendChatMessage(new CapabilityCardMessage(params.tabID)) + } +} diff --git a/packages/amazonq/src/app/amazonqScan/chat/controller/messenger/messengerUtils.ts b/packages/amazonq/src/app/amazonqScan/chat/controller/messenger/messengerUtils.ts new file mode 100644 index 00000000000..455a4ebf4af --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/chat/controller/messenger/messengerUtils.ts @@ -0,0 +1,19 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + */ +// TODO: Refactor the common functionality between Transform, FeatureDev, CWSPRChat, Scan and UTG to a new Folder. + +export default class MessengerUtils { + static stringToEnumValue = ( + enumObject: T, + value: `${T[K]}` + ): T[K] => { + if (Object.values(enumObject).includes(value)) { + return value as unknown as T[K] + } else { + throw new Error('Value provided was not found in Enum') + } + } +} diff --git a/packages/amazonq/src/app/amazonqScan/chat/session/session.ts b/packages/amazonq/src/app/amazonqScan/chat/session/session.ts new file mode 100644 index 00000000000..1ca7e8d7362 --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/chat/session/session.ts @@ -0,0 +1,25 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export enum ConversationState { + IDLE, + JOB_SUBMITTED, +} + +export class Session { + // Used to keep track of whether or not the current session is currently authenticating/needs authenticating + public isAuthenticating: boolean = false + + // A tab may or may not be currently open + public tabID: string | undefined + + public conversationState: ConversationState = ConversationState.IDLE + + constructor() {} + + public isTabOpen(): boolean { + return this.tabID !== undefined + } +} diff --git a/packages/amazonq/src/app/amazonqScan/chat/storages/chatSession.ts b/packages/amazonq/src/app/amazonqScan/chat/storages/chatSession.ts new file mode 100644 index 00000000000..b7df6eb0cc6 --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/chat/storages/chatSession.ts @@ -0,0 +1,54 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + */ + +import { Session } from '../session/session' + +export class SessionNotFoundError extends Error {} + +export class ChatSessionManager { + private static _instance: ChatSessionManager + private activeSession: Session | undefined + + constructor() {} + + public static get Instance() { + return this._instance || (this._instance = new this()) + } + + private createSession(): Session { + this.activeSession = new Session() + return this.activeSession + } + + public getSession(): Session { + if (this.activeSession === undefined) { + return this.createSession() + } + + return this.activeSession + } + + public setActiveTab(tabID: string): string { + if (this.activeSession !== undefined) { + if (!this.activeSession.isTabOpen()) { + this.activeSession.tabID = tabID + return tabID + } + return this.activeSession.tabID! + } + + throw new SessionNotFoundError() + } + + public removeActiveTab(): void { + if (this.activeSession !== undefined) { + if (this.activeSession.isTabOpen()) { + this.activeSession.tabID = undefined + return + } + } + } +} diff --git a/packages/amazonq/src/app/amazonqScan/chat/views/actions/uiMessageListener.ts b/packages/amazonq/src/app/amazonqScan/chat/views/actions/uiMessageListener.ts new file mode 100644 index 00000000000..ede78d1a0bf --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/chat/views/actions/uiMessageListener.ts @@ -0,0 +1,115 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MessageListener, ExtensionMessage } from 'aws-core-vscode/amazonq' +import { ScanChatControllerEventEmitters } from 'aws-core-vscode/amazonqScan' + +type UIMessage = ExtensionMessage & { + tabID?: string +} + +export interface UIMessageListenerProps { + readonly chatControllerEventEmitters: ScanChatControllerEventEmitters + readonly webViewMessageListener: MessageListener +} + +export class UIMessageListener { + private scanControllerEventsEmitters: ScanChatControllerEventEmitters | undefined + private webViewMessageListener: MessageListener + + constructor(props: UIMessageListenerProps) { + this.scanControllerEventsEmitters = props.chatControllerEventEmitters + this.webViewMessageListener = props.webViewMessageListener + + // Now we are listening to events that get sent from amazonq/webview/actions/actionListener (e.g. the tab) + this.webViewMessageListener.onMessage((msg) => { + this.handleMessage(msg) + }) + } + + private handleMessage(msg: ExtensionMessage) { + switch (msg.command) { + case 'new-tab-was-created': + this.tabOpened(msg) + break + case 'tab-was-removed': + this.tabClosed(msg) + break + case 'auth-follow-up-was-clicked': + this.authClicked(msg) + break + case 'review': + this.scan(msg) + break + case 'form-action-click': + this.formActionClicked(msg) + break + case 'follow-up-was-clicked': + this.followUpClicked(msg) + break + case 'response-body-link-click': + this.processResponseBodyLinkClick(msg) + break + case 'file-click': + this.processFileClick(msg) + break + } + } + + private scan(msg: UIMessage) { + this.scanControllerEventsEmitters?.runScan.fire({ + tabID: msg.tabID, + }) + } + + private formActionClicked(msg: UIMessage) { + this.scanControllerEventsEmitters?.formActionClicked.fire({ + ...msg, + }) + } + + private tabOpened(msg: UIMessage) { + this.scanControllerEventsEmitters?.tabOpened.fire({ + tabID: msg.tabID, + }) + } + + private tabClosed(msg: UIMessage) { + this.scanControllerEventsEmitters?.tabClosed.fire({ + tabID: msg.tabID, + }) + } + + private authClicked(msg: UIMessage) { + this.scanControllerEventsEmitters?.authClicked.fire({ + tabID: msg.tabID, + authType: msg.authType, + }) + } + + private followUpClicked(msg: UIMessage) { + this.scanControllerEventsEmitters?.followUpClicked.fire({ + followUp: msg.followUp, + tabID: msg.tabID, + }) + } + + private processResponseBodyLinkClick(msg: UIMessage) { + this.scanControllerEventsEmitters?.processResponseBodyLinkClick.fire({ + command: msg.command, + messageId: msg.messageId, + tabID: msg.tabID, + link: msg.link, + }) + } + + private processFileClick(msg: UIMessage) { + this.scanControllerEventsEmitters?.fileClicked.fire({ + tabID: msg.tabID, + messageId: msg.messageId, + filePath: msg.filePath, + }) + } +} diff --git a/packages/amazonq/src/app/amazonqScan/chat/views/connector/connector.ts b/packages/amazonq/src/app/amazonqScan/chat/views/connector/connector.ts new file mode 100644 index 00000000000..c906a401f91 --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/chat/views/connector/connector.ts @@ -0,0 +1,192 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AuthFollowUpType, MessagePublisher, ChatItemType } from 'aws-core-vscode/amazonq' +import { ScanMessageType } from 'aws-core-vscode/amazonqScan' +import { ChatItemButton, ProgressField, ChatItemAction, ChatItemContent } from '@aws/mynah-ui/dist/static' +import { scanChat } from '../../../models/constants' +import { MynahIcons } from '@aws/mynah-ui' + +class UiMessage { + readonly time: number = Date.now() + readonly sender: string = scanChat + readonly type: ScanMessageType = 'chatMessage' + readonly status: string = 'info' + + public constructor(protected tabID: string) {} +} + +export class AuthenticationUpdateMessage { + readonly time: number = Date.now() + readonly sender: string = scanChat + readonly type: ScanMessageType = 'authenticationUpdateMessage' + + constructor( + readonly scanEnabled: boolean, + readonly authenticatingTabIDs: string[] + ) {} +} + +export class AuthNeededException extends UiMessage { + override type: ScanMessageType = 'authNeededException' + + constructor( + readonly message: string, + readonly authType: AuthFollowUpType, + tabID: string + ) { + super(tabID) + } +} + +export interface ChatMessageProps { + readonly message: string | undefined + readonly messageId?: string | undefined + readonly messageType: ChatItemType + readonly canBeVoted?: boolean + readonly buttons?: ChatItemButton[] + readonly followUps?: ChatItemAction[] | undefined + readonly informationCard?: ChatItemContent['informationCard'] + readonly fileList?: ChatItemContent['fileList'] +} + +export class ChatMessage extends UiMessage { + readonly message: string | undefined + readonly messageId?: string | undefined + readonly messageType: ChatItemType + readonly canBeVoted?: boolean + readonly buttons: ChatItemButton[] + readonly followUps: ChatItemAction[] | undefined + readonly informationCard: ChatItemContent['informationCard'] + readonly fileList: ChatItemContent['fileList'] + override type: ScanMessageType = 'chatMessage' + + constructor(props: ChatMessageProps, tabID: string) { + super(tabID) + this.message = props.message + this.messageType = props.messageType + this.buttons = props.buttons || [] + this.messageId = props.messageId || undefined + this.followUps = props.followUps + this.informationCard = props.informationCard || undefined + this.fileList = props.fileList + this.canBeVoted = props.canBeVoted || undefined + } +} + +export class CapabilityCardMessage extends ChatMessage { + constructor(tabID: string) { + super( + { + message: '', + messageType: 'answer', + informationCard: { + title: '/review', + description: 'Included in your Q Developer subscription', + content: { + body: `I can review your workspace for vulnerabilities and issues. + +After you begin a review, I will: +1. Review all relevant code in your workspace or your current file +2. Provide a list of issues for your review + +You can then investigate, fix, or ignore issues. + +To learn more, check out our [User Guide](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security-scans.html).`, + }, + icon: 'bug' as MynahIcons, + }, + }, + tabID + ) + } +} + +export class ChatInputEnabledMessage extends UiMessage { + override type: ScanMessageType = 'chatInputEnabledMessage' + + constructor( + tabID: string, + readonly enabled: boolean + ) { + super(tabID) + } +} + +export class UpdatePlaceholderMessage extends UiMessage { + readonly newPlaceholder: string + override type: ScanMessageType = 'updatePlaceholderMessage' + + constructor(tabID: string, newPlaceholder: string) { + super(tabID) + this.newPlaceholder = newPlaceholder + } +} + +export class UpdatePromptProgressMessage extends UiMessage { + readonly progressField: ProgressField | null + override type: ScanMessageType = 'updatePromptProgress' + constructor(tabID: string, progressField: ProgressField | null) { + super(tabID) + this.progressField = progressField + } +} + +export class ErrorMessage extends UiMessage { + override type: ScanMessageType = 'errorMessage' + constructor( + readonly title: string, + readonly message: string, + tabID: string + ) { + super(tabID) + } +} + +export class ChatPrompt extends UiMessage { + readonly message: string | undefined + readonly messageType = 'system-prompt' + override type: ScanMessageType = 'chatPrompt' + constructor(message: string | undefined, tabID: string) { + super(tabID) + this.message = message + } +} + +export class AppToWebViewMessageDispatcher { + constructor(private readonly appsToWebViewMessagePublisher: MessagePublisher) {} + + public sendChatMessage(message: ChatMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendUpdatePlaceholder(message: UpdatePlaceholderMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendUpdatePromptProgress(message: UpdatePromptProgressMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendAuthenticationUpdate(message: AuthenticationUpdateMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendAuthNeededExceptionMessage(message: AuthNeededException) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendChatInputEnabled(message: ChatInputEnabledMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendErrorMessage(message: ErrorMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendPromptMessage(message: ChatPrompt) { + this.appsToWebViewMessagePublisher.publish(message) + } +} diff --git a/packages/amazonq/src/app/amazonqScan/index.ts b/packages/amazonq/src/app/amazonqScan/index.ts new file mode 100644 index 00000000000..c195193740b --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/index.ts @@ -0,0 +1,7 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export { default as MessengerUtils } from './chat/controller/messenger/messengerUtils' +export { init as scanChatAppInit } from './app' diff --git a/packages/amazonq/src/app/amazonqScan/models/constants.ts b/packages/amazonq/src/app/amazonqScan/models/constants.ts new file mode 100644 index 00000000000..93e815884e1 --- /dev/null +++ b/packages/amazonq/src/app/amazonqScan/models/constants.ts @@ -0,0 +1,99 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { ProgressField, MynahIcons, ChatItemButton } from '@aws/mynah-ui' +import { AggregatedCodeScanIssue, CodeAnalysisScope, SecurityScanStep, severities } from 'aws-core-vscode/codewhisperer' +import { i18n } from 'aws-core-vscode/shared' + +// For uniquely identifiying which chat messages should be routed to Scan +export const scanChat = 'scanChat' + +export enum ScanAction { + RUN_PROJECT_SCAN = 'runProjectScan', + RUN_FILE_SCAN = 'runFileScan', + STOP_PROJECT_SCAN = 'stopProjectScan', + STOP_FILE_SCAN = 'stopFileScan', +} + +export const cancelFileScanButton: ChatItemButton = { + id: ScanAction.STOP_FILE_SCAN, + text: i18n('AWS.generic.cancel'), + icon: 'cancel' as MynahIcons, +} + +export const cancelProjectScanButton: ChatItemButton = { + ...cancelFileScanButton, + id: ScanAction.STOP_PROJECT_SCAN, +} + +export const fileScanProgressField: ProgressField = { + status: 'default', + text: i18n('AWS.amazonq.scans.fileScanInProgress'), + value: -1, + actions: [cancelFileScanButton], +} + +export const projectScanProgressField: ProgressField = { + ...fileScanProgressField, + text: i18n('AWS.amazonq.scans.projectScanInProgress'), + actions: [cancelProjectScanButton], +} + +export const cancellingProgressField: ProgressField = { + status: 'warning', + text: i18n('AWS.generic.cancelling'), + value: -1, + actions: [], +} + +const checkIcons = { + wait: '☐', + current: '☐', + done: '☑', +} +export const scanProgressMessage = ( + currentStep: SecurityScanStep, + scope: CodeAnalysisScope, + fileName?: string +) => `Okay, I'm reviewing ${scope === CodeAnalysisScope.PROJECT ? 'your project' : fileName ? `\`${fileName}\`` : 'your file'} for code issues. + +This may take a few minutes. I'll share my progress here. + +${getIconForStep(SecurityScanStep.CREATE_SCAN_JOB, currentStep)} Initiating code review + +${getIconForStep(SecurityScanStep.POLL_SCAN_STATUS, currentStep)} Reviewing your code + +${getIconForStep(SecurityScanStep.PROCESS_SCAN_RESULTS, currentStep)} Processing review results +` + +export const scanSummaryMessage = ( + scope: CodeAnalysisScope, + securityRecommendationCollection: AggregatedCodeScanIssue[] +) => { + const severityCounts = securityRecommendationCollection.reduce( + (accumulator, current) => ({ + ...Object.fromEntries( + severities.map((severity) => [ + severity, + accumulator[severity] + + current.issues.filter((issue) => issue.severity === severity && issue.visible).length, + ]) + ), + }), + Object.fromEntries(severities.map((severity) => [severity, 0])) + ) + return `I completed the code review. I found the following issues in your ${scope === CodeAnalysisScope.PROJECT ? 'workspace' : 'file'}: +${Object.entries(severityCounts) + .map(([severity, count]) => `- ${severity}: \`${count} ${count === 1 ? 'issue' : 'issues'}\``) + .join('\n')} +` +} + +const getIconForStep = (targetStep: number, currentStep: number) => { + return currentStep === targetStep + ? checkIcons.current + : currentStep > targetStep + ? checkIcons.done + : checkIcons.wait +} diff --git a/packages/amazonq/src/app/chat/activation.ts b/packages/amazonq/src/app/chat/activation.ts index 82cc92f7b3a..f7b3f9a0fa5 100644 --- a/packages/amazonq/src/app/chat/activation.ts +++ b/packages/amazonq/src/app/chat/activation.ts @@ -9,11 +9,13 @@ import { telemetry } from 'aws-core-vscode/telemetry' import { AuthUtil, CodeWhispererSettings } from 'aws-core-vscode/codewhisperer' import { Commands, placeholder, funcUtil } from 'aws-core-vscode/shared' import * as amazonq from 'aws-core-vscode/amazonq' +import { scanChatAppInit } from '../amazonqScan' +import { init as inlineChatInit } from '../../inlineChat/app' export async function activate(context: ExtensionContext) { const appInitContext = amazonq.DefaultAmazonQAppInitContext.instance - registerApps(appInitContext) + registerApps(appInitContext, context) const provider = new amazonq.AmazonQChatViewProvider( context, @@ -25,7 +27,11 @@ export async function activate(context: ExtensionContext) { await amazonq.TryChatCodeLensProvider.register(appInitContext.onDidChangeAmazonQVisibility.event) const setupLsp = funcUtil.debounce(async () => { - void amazonq.LspController.instance.trySetupLsp(context) + void amazonq.LspController.instance.trySetupLsp(context, { + startUrl: AuthUtil.instance.startUrl, + maxIndexSize: CodeWhispererSettings.instance.getMaxIndexSize(), + isVectorIndexEnabled: CodeWhispererSettings.instance.isLocalIndexEnabled(), + }) }, 5000) context.subscriptions.push( @@ -60,10 +66,14 @@ export async function activate(context: ExtensionContext) { void setupAuthNotification() } -function registerApps(appInitContext: amazonq.AmazonQAppInitContext) { +function registerApps(appInitContext: amazonq.AmazonQAppInitContext, context: ExtensionContext) { amazonq.cwChatAppInit(appInitContext) amazonq.featureDevChatAppInit(appInitContext) amazonq.gumbyChatAppInit(appInitContext) + amazonq.testChatAppInit(appInitContext) + scanChatAppInit(appInitContext) + amazonq.docChatAppInit(appInitContext) + inlineChatInit(context) } /** diff --git a/packages/amazonq/src/extension.ts b/packages/amazonq/src/extension.ts index 095cc4b3df4..a4b53dbf66d 100644 --- a/packages/amazonq/src/extension.ts +++ b/packages/amazonq/src/extension.ts @@ -3,28 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - AuthUtils, - CredentialsStore, - LoginManager, - getTelemetryMetadataForConn, - initializeAuth, - isAnySsoConnection, -} from 'aws-core-vscode/auth' -import { - AuthState, - AuthUtil, - activate as activateCodeWhisperer, - shutdown as shutdownCodeWhisperer, -} from 'aws-core-vscode/codewhisperer' +import { AuthUtils, CredentialsStore, LoginManager, initializeAuth } from 'aws-core-vscode/auth' +import { activate as activateCodeWhisperer, shutdown as shutdownCodeWhisperer } from 'aws-core-vscode/codewhisperer' import { makeEndpointsProvider, registerGenericCommands } from 'aws-core-vscode' import { CommonAuthWebview } from 'aws-core-vscode/login' import { + amazonQDiffScheme, DefaultAWSClientBuilder, DefaultAwsContext, ExtContext, RegionProvider, Settings, + VirtualFileSystem, + VirtualMemoryFile, activateLogger, activateTelemetry, env, @@ -35,13 +26,13 @@ import { globals, initialize, initializeComputeRegion, - isNetworkError, messages, placeholder, setContext, setupUninstallHandler, + maybeShowMinVscodeWarning, } from 'aws-core-vscode/shared' -import { ExtStartUpSources, telemetry } from 'aws-core-vscode/telemetry' +import { ExtStartUpSources } from 'aws-core-vscode/telemetry' import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils' import { join } from 'path' import * as semver from 'semver' @@ -62,7 +53,7 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is errors.init(fs.getUsername(), env.isAutomation()) await initializeComputeRegion() - globals.contextPrefix = 'amazonq.' //todo: disconnect from above line + globals.contextPrefix = 'amazonq.' // todo: disconnect from above line // Avoid activation if older toolkit is installed // Amazon Q is only compatible with AWS Toolkit >= 3.0.0 @@ -98,16 +89,16 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is } } + void maybeShowMinVscodeWarning('1.83.0') + globals.machineId = await getMachineId() globals.awsContext = new DefaultAwsContext() globals.sdkClientBuilder = new DefaultAWSClientBuilder(globals.awsContext) globals.manifestPaths.endpoints = context.asAbsolutePath(join('resources', 'endpoints.json')) globals.regionProvider = RegionProvider.fromEndpointsProvider(makeEndpointsProvider()) - const qOutputChannel = vscode.window.createOutputChannel('Amazon Q', { log: true }) const qLogChannel = vscode.window.createOutputChannel('Amazon Q Logs', { log: true }) - await activateLogger(context, amazonQContextPrefix, qOutputChannel, qLogChannel) - globals.outputChannel = qOutputChannel + await activateLogger(context, amazonQContextPrefix, qLogChannel) globals.logOutputChannel = qLogChannel globals.loginManager = new LoginManager(globals.awsContext, new CredentialsStore()) @@ -131,7 +122,15 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is registerCommands(context) // Handle Amazon Q Extension un-installation. - setupUninstallHandler(VSCODE_EXTENSION_ID.amazonq, context) + setupUninstallHandler(VSCODE_EXTENSION_ID.amazonq, context.extension.packageJSON.version, context) + + const vfs = new VirtualFileSystem() + + // Register an empty file that's used when a to open a diff + vfs.registerProvider( + vscode.Uri.from({ scheme: amazonQDiffScheme, path: 'empty' }), + new VirtualMemoryFile(new Uint8Array()) + ) // Hide the Amazon Q tree in toolkit explorer await setContext('aws.toolkit.amazonq.dismissed', true) @@ -146,53 +145,6 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is void focusAmazonQPanel.execute(placeholder, 'firstStartUp') }, 1000) } - - await telemetry.auth_userState - .run(async () => { - telemetry.record({ passive: true }) - - const firstUse = AuthUtils.ExtensionUse.instance.isFirstUse() - const wasUpdated = AuthUtils.ExtensionUse.instance.wasUpdated() - - if (firstUse) { - telemetry.record({ source: ExtStartUpSources.firstStartUp }) - } else if (wasUpdated) { - telemetry.record({ source: ExtStartUpSources.update }) - } else { - telemetry.record({ source: ExtStartUpSources.reload }) - } - - let authState: AuthState = 'disconnected' - try { - // May call connection validate functions that try to refresh the token. - // This could result in network errors. - authState = (await AuthUtil.instance.getChatAuthState(false)).codewhispererChat - } catch (err) { - if ( - isNetworkError(err) && - AuthUtil.instance.conn && - AuthUtil.instance.auth.getConnectionState(AuthUtil.instance.conn) === 'valid' - ) { - authState = 'connectedWithNetworkError' - } else { - throw err - } - } - const currConn = AuthUtil.instance.conn - if (currConn !== undefined && !isAnySsoConnection(currConn)) { - getLogger().error(`Current Amazon Q connection is not SSO, type is: %s`, currConn?.type) - } - - telemetry.record({ - authStatus: - authState === 'connected' || authState === 'expired' || authState === 'connectedWithNetworkError' - ? authState - : 'notConnected', - authEnabledConnections: AuthUtils.getAuthFormIdsFromConnection(currConn).join(','), - ...(await getTelemetryMetadataForConn(currConn)), - }) - }) - .catch((err) => getLogger().error('Error collecting telemetry for auth_userState: %s', err)) } export async function deactivateCommon() { diff --git a/packages/amazonq/src/extensionNode.ts b/packages/amazonq/src/extensionNode.ts index 84bc76290b3..6fe0c28ad8f 100644 --- a/packages/amazonq/src/extensionNode.ts +++ b/packages/amazonq/src/extensionNode.ts @@ -7,16 +7,20 @@ import * as vscode from 'vscode' import { activateAmazonQCommon, amazonQContextPrefix, deactivateCommon } from './extension' import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' import { activate as activateQGumby } from 'aws-core-vscode/amazonqGumby' -import { ExtContext, globals } from 'aws-core-vscode/shared' +import { ExtContext, globals, CrashMonitoring, getLogger, isNetworkError, isSageMaker } from 'aws-core-vscode/shared' import { filetypes, SchemaService } from 'aws-core-vscode/sharedNode' import { updateDevMode } from 'aws-core-vscode/dev' import { CommonAuthViewProvider } from 'aws-core-vscode/login' import { isExtensionActive, VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils' import { registerSubmitFeedback } from 'aws-core-vscode/feedback' import { DevOptions } from 'aws-core-vscode/dev' -import { Auth } from 'aws-core-vscode/auth' +import { Auth, AuthUtils, getTelemetryMetadataForConn, isAnySsoConnection } from 'aws-core-vscode/auth' import api from './api' import { activate as activateCWChat } from './app/chat/activation' +import { beta } from 'aws-core-vscode/dev' +import { activate as activateNotifications, NotificationsController } from 'aws-core-vscode/notifications' +import { AuthState, AuthUtil } from 'aws-core-vscode/codewhisperer' +import { telemetry, AuthUserState } from 'aws-core-vscode/telemetry' export async function activate(context: vscode.ExtensionContext) { // IMPORTANT: No other code should be added to this function. Place it in one of the following 2 functions where appropriate. @@ -32,6 +36,9 @@ export async function activate(context: vscode.ExtensionContext) { * the code compatible with web and move it to {@link activateAmazonQCommon}. */ async function activateAmazonQNode(context: vscode.ExtensionContext) { + // Intentionally do not await since this is slow and non-critical + void (await CrashMonitoring.instance())?.start() + const extContext = { extensionContext: context, } @@ -56,6 +63,50 @@ async function activateAmazonQNode(context: vscode.ExtensionContext) { filetypes.activate() await setupDevMode(context) + await beta.activate(context) + + // TODO: Should probably emit for web as well. + // Will the web metric look the same? + telemetry.auth_userState.emit({ + passive: true, + result: 'Succeeded', + source: AuthUtils.ExtensionUse.instance.sourceForTelemetry(), + ...(await getAuthState()), + }) + + void activateNotifications(context, getAuthState) +} + +async function getAuthState(): Promise> { + let authState: AuthState = 'disconnected' + try { + // May call connection validate functions that try to refresh the token. + // This could result in network errors. + authState = (await AuthUtil.instance.getChatAuthState(false)).codewhispererChat + } catch (err) { + if ( + isNetworkError(err) && + AuthUtil.instance.conn && + AuthUtil.instance.auth.getConnectionState(AuthUtil.instance.conn) === 'valid' + ) { + authState = 'connectedWithNetworkError' + } else { + throw err + } + } + const currConn = AuthUtil.instance.conn + if (currConn !== undefined && !(isAnySsoConnection(currConn) || isSageMaker())) { + getLogger().error(`Current Amazon Q connection is not SSO, type is: %s`, currConn?.type) + } + + return { + authStatus: + authState === 'connected' || authState === 'expired' || authState === 'connectedWithNetworkError' + ? authState + : 'notConnected', + authEnabledConnections: AuthUtils.getAuthFormIdsFromConnection(currConn).join(','), + ...(await getTelemetryMetadataForConn(currConn)), + } } /** @@ -70,13 +121,17 @@ async function setupDevMode(context: vscode.ExtensionContext) { const devOptions: DevOptions = { context, - auth: Auth.instance, + auth: () => Auth.instance, + notificationsController: () => NotificationsController.instance, menuOptions: [ 'editStorage', + 'resetState', 'showEnvVars', 'deleteSsoConnections', 'expireSsoConnections', 'editAuthConnections', + 'notificationsSend', + 'forceIdeCrash', ], } @@ -92,5 +147,6 @@ async function setupDevMode(context: vscode.ExtensionContext) { } export async function deactivate() { - await deactivateCommon() + // Run concurrently to speed up execution. stop() does not throw so it is safe + await Promise.all([(await CrashMonitoring.instance())?.shutdown(), deactivateCommon()]) } diff --git a/packages/amazonq/src/inlineChat/app.ts b/packages/amazonq/src/inlineChat/app.ts new file mode 100644 index 00000000000..f783ef8d84f --- /dev/null +++ b/packages/amazonq/src/inlineChat/app.ts @@ -0,0 +1,12 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import { InlineChatController } from '../inlineChat/controller/inlineChatController' +import { registerInlineCommands } from '../inlineChat/command/registerInlineCommands' + +export function init(context: vscode.ExtensionContext) { + const inlineChatController = new InlineChatController(context) + registerInlineCommands(context, inlineChatController) +} diff --git a/packages/amazonq/src/inlineChat/codeLenses/codeLenseProvider.ts b/packages/amazonq/src/inlineChat/codeLenses/codeLenseProvider.ts new file mode 100644 index 00000000000..34a85e10b38 --- /dev/null +++ b/packages/amazonq/src/inlineChat/codeLenses/codeLenseProvider.ts @@ -0,0 +1,76 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import * as os from 'os' +import { InlineTask, TaskState } from '../controller/inlineTask' + +export class CodelensProvider implements vscode.CodeLensProvider { + private codeLenses: vscode.CodeLens[] = [] + private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter() + public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event + + constructor(context: vscode.ExtensionContext) { + context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', this)) + this.provideCodeLenses = this.provideCodeLenses.bind(this) + } + + public provideCodeLenses(_document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.CodeLens[] { + return this.codeLenses + } + + public updateLenses(task: InlineTask): void { + if (task.state === TaskState.Complete) { + this.codeLenses = [] + this._onDidChangeCodeLenses.fire() + return + } + switch (task.state) { + case TaskState.InProgress: { + this.codeLenses = [] + this.codeLenses.push( + new vscode.CodeLens(new vscode.Range(task.selectedRange.start, task.selectedRange.start), { + title: 'Amazon Q is generating...', + command: '', + }) + ) + break + } + case TaskState.WaitingForDecision: { + let acceptTitle: string + let rejectTitle: string + if (os.platform() === 'darwin') { + acceptTitle = 'Accept ($(newline))' + rejectTitle = `Reject ( \u238B )` + } else { + acceptTitle = 'Accept (Enter)' + rejectTitle = `Reject (Esc)` + } + + this.codeLenses = [] + this.codeLenses.push( + new vscode.CodeLens(new vscode.Range(task.selectedRange.start, task.selectedRange.start), { + title: acceptTitle, + command: 'aws.amazonq.inline.waitForUserDecisionAcceptAll', + arguments: [task], + }) + ) + this.codeLenses.push( + new vscode.CodeLens(new vscode.Range(task.selectedRange.start, task.selectedRange.start), { + title: rejectTitle, + command: 'aws.amazonq.inline.waitForUserDecisionRejectAll', + arguments: [task], + }) + ) + break + } + default: { + this.codeLenses = [] + break + } + } + this._onDidChangeCodeLenses.fire() + } +} diff --git a/packages/amazonq/src/inlineChat/command/registerInlineCommands.ts b/packages/amazonq/src/inlineChat/command/registerInlineCommands.ts new file mode 100644 index 00000000000..48b1fbf5145 --- /dev/null +++ b/packages/amazonq/src/inlineChat/command/registerInlineCommands.ts @@ -0,0 +1,22 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import vscode from 'vscode' +import { InlineChatController } from '../controller/inlineChatController' +import { InlineTask } from '../controller/inlineTask' + +export function registerInlineCommands(context: vscode.ExtensionContext, inlineChatController: InlineChatController) { + context.subscriptions.push( + vscode.commands.registerCommand('aws.amazonq.inline.invokeChat', async () => { + await inlineChatController.inlineQuickPick() + }), + vscode.commands.registerCommand('aws.amazonq.inline.waitForUserDecisionAcceptAll', async (task: InlineTask) => { + await inlineChatController.acceptAllChanges(task, true) + }), + vscode.commands.registerCommand('aws.amazonq.inline.waitForUserDecisionRejectAll', async (task: InlineTask) => { + await inlineChatController.rejectAllChanges(task, true) + }) + ) +} diff --git a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts new file mode 100644 index 00000000000..ce0df6a0878 --- /dev/null +++ b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts @@ -0,0 +1,412 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { randomUUID } from 'crypto' +import * as vscode from 'vscode' +import { InlineDecorator } from '../decorations/inlineDecorator' +import { InlineChatProvider } from '../provider/inlineChatProvider' +import { InlineTask, TaskState, TextDiff } from './inlineTask' +import { responseTransformer } from '../output/responseTransformer' +import { adjustTextDiffForEditing, computeDiff } from '../output/computeDiff' +import { computeDecorations } from '../decorations/computeDecorations' +import { CodelensProvider } from '../codeLenses/codeLenseProvider' +import { PromptMessage, ReferenceLogController } from 'aws-core-vscode/codewhispererChat' +import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer' +import { + codicon, + getIcon, + getLogger, + messages, + setContext, + Timeout, + textDocumentUtil, + isSageMaker, +} from 'aws-core-vscode/shared' +import { InlineLineAnnotationController } from '../decorations/inlineLineAnnotationController' + +export class InlineChatController { + private task: InlineTask | undefined + private readonly decorator = new InlineDecorator() + private readonly inlineChatProvider: InlineChatProvider + private readonly codeLenseProvider: CodelensProvider + private readonly referenceLogController = new ReferenceLogController() + private readonly inlineLineAnnotationController: InlineLineAnnotationController + private userQuery: string | undefined + private listeners: vscode.Disposable[] = [] + + constructor(context: vscode.ExtensionContext) { + this.inlineChatProvider = new InlineChatProvider() + this.inlineChatProvider.onErrorOccured(() => this.handleError()) + this.codeLenseProvider = new CodelensProvider(context) + this.inlineLineAnnotationController = new InlineLineAnnotationController(context) + } + + public async createTask( + query: string, + document: vscode.TextDocument, + selectionRange: vscode.Selection + ): Promise { + const inlineTask = new InlineTask(query, document, selectionRange) + return inlineTask + } + + public async acceptAllChanges(task = this.task, userInvoked: boolean): Promise { + if (!task) { + return + } + const editor = vscode.window.visibleTextEditors.find( + (editor) => editor.document.uri.toString() === task.document.uri.toString() + ) + if (!editor) { + return + } + if (userInvoked) { + this.inlineChatProvider.sendTelemetryEvent( + { + userDecision: 'ACCEPT', + }, + this.task + ) + } + const deletions = task.diff.filter((diff) => diff.type === 'deletion') + await editor.edit( + (editBuilder) => { + for (const deletion of deletions) { + editBuilder.delete(deletion.range) + } + }, + { undoStopAfter: false, undoStopBefore: false } + ) + task.diff = [] + task.updateDecorations() + this.decorator.applyDecorations(task) + await this.updateTaskAndLenses(task) + this.referenceLogController.addReferenceLog(task.codeReferences, task.replacement ? task.replacement : '') + await this.reset() + } + + public async rejectAllChanges(task = this.task, userInvoked: boolean): Promise { + if (!task) { + return + } + const editor = vscode.window.visibleTextEditors.find( + (editor) => editor.document.uri.toString() === task.document.uri.toString() + ) + if (!editor) { + return + } + if (userInvoked) { + this.inlineChatProvider.sendTelemetryEvent( + { + userDecision: 'REJECT', + }, + this.task + ) + } + const insertions = task.diff.filter((diff) => diff.type === 'insertion') + await editor.edit( + (editBuilder) => { + for (const insertion of insertions) { + editBuilder.delete(insertion.range) + } + }, + { undoStopAfter: false, undoStopBefore: false } + ) + task.diff = [] + task.updateDecorations() + this.decorator.applyDecorations(task) + await this.updateTaskAndLenses(task) + this.referenceLogController.addReferenceLog(task.codeReferences, task.replacement ? task.replacement : '') + await this.reset() + } + + public async updateTaskAndLenses(task?: InlineTask, taskState?: TaskState) { + if (!task) { + return + } + if (taskState) { + task.state = taskState + } else if (!task.diff || task.diff.length === 0) { + // If the previous state was waiting for a decision and the code diff is clean, then we mark the task as completed + if (task.state === TaskState.WaitingForDecision) { + task.state = TaskState.Complete + } + } + this.codeLenseProvider.updateLenses(task) + if (task.state === TaskState.InProgress) { + if (vscode.window.activeTextEditor) { + await this.inlineLineAnnotationController.hide(vscode.window.activeTextEditor) + } + } + await this.refreshCodeLenses(task) + if (task.state === TaskState.Complete) { + await this.reset() + } + } + + private async handleError() { + if (!this.task) { + return + } + this.task.state = TaskState.Error + this.codeLenseProvider.updateLenses(this.task) + await this.refreshCodeLenses(this.task) + await this.reset() + } + + private async reset() { + this.listeners.forEach((listener) => listener.dispose()) + this.listeners = [] + + this.task = undefined + this.inlineLineAnnotationController.enable() + await setContext('amazonq.inline.codelensShortcutEnabled', undefined) + } + + private async refreshCodeLenses(task: InlineTask): Promise { + await vscode.commands.executeCommand('vscode.executeCodeLensProvider', task.document.uri) + } + + public async inlineQuickPick(previouseQuery?: string) { + const editor = vscode.window.activeTextEditor + if (!editor) { + return + } + + if (isSageMaker()) { + void vscode.window.showWarningMessage('Amazon Q: Inline chat is not supported in Sagemaker') + return + } + + if (this.task && this.task.isActiveState()) { + void vscode.window.showWarningMessage( + 'Amazon Q: Reject or Accept the current suggestion before creating a new one' + ) + return + } + + await vscode.window + .showInputBox({ + value: previouseQuery ?? '', + placeHolder: 'Enter instructions for Q', + prompt: codicon`${getIcon('aws-amazonq-q-white')} Edit code`, + }) + .then(async (query) => { + if (!query || query.trim() === '') { + getLogger().info('inlineQuickPick query is empty') + return + } + + this.userQuery = query + await textDocumentUtil.addEofNewline(editor) + this.task = await this.createTask(query, editor.document, editor.selection) + await this.inlineLineAnnotationController.disable(editor) + await this.computeDiffAndRenderOnEditor(query, editor.document).catch(async (err) => { + getLogger().error('computeDiffAndRenderOnEditor error: %s', (err as Error)?.message) + if (err instanceof Error) { + void vscode.window.showErrorMessage(`Amazon Q: ${err.message}`) + } else { + void vscode.window.showErrorMessage('Amazon Q encountered an error') + } + await this.handleError() + }) + }) + } + + private async computeDiffAndRenderOnEditor(query: string, document: vscode.TextDocument) { + if (!this.task) { + return + } + + await this.updateTaskAndLenses(this.task, TaskState.InProgress) + getLogger().info(`inline chat query:\n${query}`) + const uuid = randomUUID() + const message: PromptMessage = { + message: query, + messageId: uuid, + command: undefined, + userIntent: undefined, + tabID: uuid, + } + + const requestStart = performance.now() + let responseStartLatency: number | undefined + + const response = await this.inlineChatProvider.processPromptMessage(message) + this.task.requestId = response?.$metadata.requestId + + // Deselect all code + const editor = vscode.window.activeTextEditor + if (editor) { + const selection = editor.selection + if (!selection.isEmpty) { + const cursor = selection.active + const newSelection = new vscode.Selection(cursor, cursor) + editor.selection = newSelection + } + } + + if (response) { + let qSuggestedCodeResponse = '' + for await (const chatEvent of response.generateAssistantResponseResponse!) { + if ( + chatEvent.assistantResponseEvent?.content !== undefined && + chatEvent.assistantResponseEvent.content.length > 0 + ) { + if (responseStartLatency === undefined) { + responseStartLatency = performance.now() - requestStart + } + + qSuggestedCodeResponse += chatEvent.assistantResponseEvent.content + + const transformedResponse = responseTransformer(qSuggestedCodeResponse, this.task, false) + if (transformedResponse) { + const textDiff = computeDiff(transformedResponse, this.task, true) + const decorations = computeDecorations(this.task) + this.task.decorations = decorations + await this.applyDiff(this.task!, textDiff ?? [], { + undoStopBefore: false, + undoStopAfter: false, + }) + this.decorator.applyDecorations(this.task) + this.task.previouseDiff = textDiff + } + } + if ( + chatEvent.codeReferenceEvent?.references !== undefined && + chatEvent.codeReferenceEvent.references.length > 0 + ) { + this.task.codeReferences = this.task.codeReferences.concat(chatEvent.codeReferenceEvent?.references) + // clear diff if user settings is off for code reference + if (!CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled()) { + await this.rejectAllChanges(this.task, false) + void vscode.window.showInformationMessage( + 'Your settings do not allow code generation with references.' + ) + await this.updateTaskAndLenses(this.task, TaskState.Complete) + return + } + } + if (chatEvent.error) { + getLogger().error('generateAssistantResponse stream error: %s', chatEvent.error) + await this.rejectAllChanges(this.task, false) + void vscode.window.showErrorMessage(`Amazon Q: ${chatEvent.error.message}`) + await this.updateTaskAndLenses(this.task, TaskState.Complete) + return + } + } + + if (this.task) { + // Unclear why we need to check if task is defined, but occasionally an error occurs otherwise + this.task.responseStartLatency = responseStartLatency + this.task.responseEndLatency = performance.now() - requestStart + } + getLogger().info(`qSuggestedCodeResponse:\n${qSuggestedCodeResponse}`) + const transformedResponse = responseTransformer(qSuggestedCodeResponse, this.task, true) + if (transformedResponse) { + const textDiff = computeDiff(transformedResponse, this.task, false) + const decorations = computeDecorations(this.task) + this.task.decorations = decorations + await this.applyDiff(this.task, textDiff ?? []) + this.decorator.applyDecorations(this.task) + await this.updateTaskAndLenses(this.task, TaskState.WaitingForDecision) + await setContext('amazonq.inline.codelensShortcutEnabled', true) + this.undoListener(this.task) + } else { + void messages.showMessageWithCancel( + 'No suggestions from Q, please try different instructions.', + new Timeout(5000) + ) + await this.updateTaskAndLenses(this.task, TaskState.Complete) + await this.inlineQuickPick(this.userQuery) + await this.handleError() + } + } + } + + private async applyDiff( + task: InlineTask, + textDiff: TextDiff[], + undoOption?: { undoStopBefore: boolean; undoStopAfter: boolean } + ) { + const adjustedTextDiff = adjustTextDiffForEditing(textDiff) + const visibleEditor = vscode.window.visibleTextEditors.find( + (editor) => editor.document.uri === task.document.uri + ) + const previousDiff = task.previouseDiff?.filter((diff) => diff.type === 'insertion') + + if (visibleEditor) { + if (previousDiff) { + await visibleEditor.edit( + (editBuilder) => { + for (const insertion of previousDiff) { + editBuilder.delete(insertion.range) + } + }, + { undoStopAfter: false, undoStopBefore: false } + ) + } + await visibleEditor.edit( + (editBuilder) => { + for (const change of adjustedTextDiff) { + if (change.type === 'insertion') { + editBuilder.insert(change.range.start, change.replacementText) + } + } + }, + undoOption ?? { undoStopBefore: true, undoStopAfter: false } + ) + } else { + if (previousDiff) { + const edit = new vscode.WorkspaceEdit() + for (const insertion of previousDiff) { + edit.delete(task.document.uri, insertion.range) + } + await vscode.workspace.applyEdit(edit) + } + const edit = new vscode.WorkspaceEdit() + for (const change of textDiff) { + if (change.type === 'insertion') { + edit.insert(task.document.uri, change.range.start, change.replacementText) + } + } + await vscode.workspace.applyEdit(edit) + } + } + + private undoListener(task: InlineTask) { + const listener: vscode.Disposable = vscode.workspace.onDidChangeTextDocument(async (event) => { + const { document, contentChanges } = event + + if (document.uri.toString() !== task.document.uri.toString()) { + return + } + + const changeIntersectsRange = contentChanges.some((change) => { + const { range } = change + if (task.selectedRange) { + return !( + range.end.isBefore(task.selectedRange.start) || range.start.isAfter(task.selectedRange.end) + ) + } + }) + + if (!changeIntersectsRange) { + return + } + + const updatedSelectedText = document.getText(task.selectedRange) + + if (updatedSelectedText.trim() === task.selectedText.trim()) { + task.diff = [] + await this.updateTaskAndLenses(task) + task.updateDecorations() + this.decorator.applyDecorations(task) + listener.dispose() + } + }) + + this.listeners.push(listener) + } +} diff --git a/packages/amazonq/src/inlineChat/controller/inlineTask.ts b/packages/amazonq/src/inlineChat/controller/inlineTask.ts new file mode 100644 index 00000000000..a6a169ad58c --- /dev/null +++ b/packages/amazonq/src/inlineChat/controller/inlineTask.ts @@ -0,0 +1,160 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import type { CodeReference } from 'aws-core-vscode/amazonq' +import type { InlineChatEvent } from 'aws-core-vscode/codewhisperer' +import type { Decorations } from '../decorations/inlineDecorator' +import { computeDecorations } from '../decorations/computeDecorations' +import { extractLanguageNameFromFile } from 'aws-core-vscode/codewhispererChat' +import { textDocumentUtil } from 'aws-core-vscode/shared' + +interface TextToInsert { + type: 'insertion' + replacementText: string + range: vscode.Range +} + +interface TextToDelete { + type: 'deletion' + originalText: string + range: vscode.Range +} + +interface DiffBlock { + originalText: string + replacementText: string + range: vscode.Range +} + +export type TextDiff = TextToInsert | TextToDelete + +export enum TaskState { + Idle = 'Idle', + InProgress = 'InProgress', + WaitingForDecision = 'WaitingForDecision', + Complete = 'Complete', + Error = 'Error', +} + +export class InlineTask { + public state: TaskState = TaskState.Idle + public diff: TextDiff[] = [] + public decorations: Decorations | undefined + public diffBlock: DiffBlock[] = [] + public codeReferences: CodeReference[] = [] + public selectedText: string + public languageName: string | undefined + + public partialSelectedText: string | undefined + public partialSelectedTextRight: string | undefined + + public previouseDiff: TextDiff[] | undefined + public selectedRange: vscode.Range + public inProgressReplacement: string | undefined + public replacement: string | undefined + + // Telemetry fields + public requestId?: string + public responseStartLatency?: number + public responseEndLatency?: number + + constructor( + public query: string, + public document: vscode.TextDocument, + selection: vscode.Selection + ) { + this.selectedRange = textDocumentUtil.expandSelectionToFullLines(document, selection) + this.selectedText = document.getText(this.selectedRange) + this.languageName = extractLanguageNameFromFile(document) + } + + public revertDiff(): void { + this.diff = [] + this.decorations = { + linesAdded: [], + linesRemoved: [], + } + } + + public removeDiffChangeByRange(range: vscode.Range): void { + if (this.diff) { + this.diff = this.diff.filter((change) => !change.range.isEqual(range)) + } + } + + public updateDecorations(): void { + const isEmpty = + !this.decorations || + (this.decorations?.linesAdded?.length === 0 && this.decorations?.linesRemoved?.length === 0) + + if (isEmpty) { + return + } + const updatedDecorations = computeDecorations(this) + this.decorations = updatedDecorations + } + + public updateDiff(affectedRange: vscode.Range, deletedLines: number) { + const diffsAfter = this.diff.filter((edit) => edit.range.start.isAfter(affectedRange.end)) + for (const diff of diffsAfter) { + diff.range = new vscode.Range( + diff.range.start.translate(-deletedLines), + diff.range.end.translate(-deletedLines) + ) + } + } + + // Telemetry methods + public get numSelectedLines() { + return this.selectedText.split('\n').length + } + + public get inputLength() { + return this.query.length + } + + public inlineChatEventBase() { + let numSuggestionAddChars = 0 + let numSuggestionAddLines = 0 + let numSuggestionDelChars = 0 + let numSuggestionDelLines = 0 + + for (const diff of this.diff) { + if (diff.type === 'insertion') { + numSuggestionAddChars += diff.replacementText.length + numSuggestionAddLines += diff.range.end.line - diff.range.start.line + 1 + } else { + numSuggestionDelChars += diff.originalText.length + numSuggestionDelLines += diff.range.end.line - diff.range.start.line + 1 + } + } + + const programmingLanguage = this.languageName + ? { + languageName: this.languageName, + } + : undefined + + const event: Partial = { + requestId: this.requestId, + timestamp: new Date(), + inputLength: this.inputLength, + numSelectedLines: this.numSelectedLines, + codeIntent: true, + responseStartLatency: this.responseStartLatency, + responseEndLatency: this.responseEndLatency, + numSuggestionAddChars, + numSuggestionAddLines, + numSuggestionDelChars, + numSuggestionDelLines, + programmingLanguage, + } + return event + } + + public isActiveState() { + return !(this.state === TaskState.Complete || this.state === TaskState.Error) + } +} diff --git a/packages/amazonq/src/inlineChat/decorations/computeDecorations.ts b/packages/amazonq/src/inlineChat/decorations/computeDecorations.ts new file mode 100644 index 00000000000..fb289494b3f --- /dev/null +++ b/packages/amazonq/src/inlineChat/decorations/computeDecorations.ts @@ -0,0 +1,32 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import { InlineTask } from '../controller/inlineTask' +import { Decorations } from './inlineDecorator' + +export function computeDecorations(task: InlineTask): Decorations | undefined { + if (!task.diff) { + return + } + + const decorations: Decorations = { + linesAdded: [], + linesRemoved: [], + } + + for (const edit of task.diff) { + const countChanged = edit.range.end.line - edit.range.start.line - 1 + if (edit.type === 'deletion') { + decorations.linesRemoved.push({ + range: new vscode.Range(edit.range.start.line, 0, edit.range.start.line + countChanged, 0), + }) + } else if (edit.type === 'insertion') { + decorations.linesAdded.push({ + range: new vscode.Range(edit.range.start.line, 0, edit.range.start.line + countChanged, 0), + }) + } + } + return decorations +} diff --git a/packages/amazonq/src/inlineChat/decorations/inlineDecorator.ts b/packages/amazonq/src/inlineChat/decorations/inlineDecorator.ts new file mode 100644 index 00000000000..3c4e3899e13 --- /dev/null +++ b/packages/amazonq/src/inlineChat/decorations/inlineDecorator.ts @@ -0,0 +1,38 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { InlineTask } from '../controller/inlineTask' + +export interface Decorations { + linesAdded: vscode.DecorationOptions[] + linesRemoved: vscode.DecorationOptions[] +} + +const removedTextDecorationType = vscode.window.createTextEditorDecorationType({ + backgroundColor: 'rgba(255, 0, 0, 0.1)', + isWholeLine: true, +}) + +const AddedTextDecorationType = vscode.window.createTextEditorDecorationType({ + backgroundColor: 'rgba(0, 255, 0, 0.1)', + isWholeLine: true, +}) + +export class InlineDecorator { + public applyDecorations(task: InlineTask): void { + const decorations = task.decorations + if (!decorations) { + return + } + const editors = vscode.window.visibleTextEditors.filter( + (editor) => editor.document.uri.toString() === task.document.uri.toString() + ) + for (const editor of editors) { + editor.setDecorations(AddedTextDecorationType, decorations.linesAdded ?? []) + editor.setDecorations(removedTextDecorationType, decorations.linesRemoved ?? []) + } + } +} diff --git a/packages/amazonq/src/inlineChat/decorations/inlineLineAnnotationController.ts b/packages/amazonq/src/inlineChat/decorations/inlineLineAnnotationController.ts new file mode 100644 index 00000000000..9ec5e08122d --- /dev/null +++ b/packages/amazonq/src/inlineChat/decorations/inlineLineAnnotationController.ts @@ -0,0 +1,57 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Container } from 'aws-core-vscode/codewhisperer' +import * as vscode from 'vscode' + +export class InlineLineAnnotationController { + private enabled: boolean = true + + constructor(context: vscode.ExtensionContext) { + context.subscriptions.push( + vscode.window.onDidChangeTextEditorSelection(async ({ selections, textEditor }) => { + let showShow = false + + if (this.enabled) { + for (const selection of selections) { + if (selection.end.line === selection.start.line + 1 && selection.end.character === 0) { + // dont show if the selection is just a newline + } else if (selection.start.line !== selection.end.line) { + showShow = true + break + } + } + } + + await this.setVisible(textEditor, showShow) + }, this) + ) + } + + private async setVisible(editor: vscode.TextEditor, visible: boolean) { + let needsRefresh: boolean + if (visible) { + needsRefresh = await Container.instance.lineAnnotationController.tryShowInlineHint() + } else { + needsRefresh = await Container.instance.lineAnnotationController.tryHideInlineHint() + } + if (needsRefresh) { + await Container.instance.lineAnnotationController.refresh(editor, 'codewhisperer') + } + } + + async hide(editor: vscode.TextEditor) { + await this.setVisible(editor, false) + } + + enable() { + this.enabled = true + } + + async disable(editor: vscode.TextEditor) { + this.enabled = false + await this.setVisible(editor, false) + } +} diff --git a/packages/amazonq/src/inlineChat/output/computeDiff.ts b/packages/amazonq/src/inlineChat/output/computeDiff.ts new file mode 100644 index 00000000000..9b599b2eff3 --- /dev/null +++ b/packages/amazonq/src/inlineChat/output/computeDiff.ts @@ -0,0 +1,116 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { type LinesOptions, diffLines, Change } from 'diff' +import * as vscode from 'vscode' +import { InlineTask, TextDiff } from '../controller/inlineTask' + +export function computeDiff(response: string, inlineTask: InlineTask, isPartialDiff: boolean): TextDiff[] | undefined { + if (!response) { + return + } + const selectedRange = inlineTask.selectedRange + const partialSelectedText = inlineTask.partialSelectedText ?? '' + const selectedText = isPartialDiff ? partialSelectedText : inlineTask.selectedText + + const normalizedResponse = + getLeadingWhitespace(selectedText) + response.trim() + getTrailingWhitespace(selectedText) + + const diffs = diffLines(selectedText, normalizedResponse, { + stripTrailingCr: true, + ignoreNewlineAtEof: true, + } as LinesOptions) + + const textDiff: TextDiff[] = [] + let startLine = selectedRange.start.line + + diffs.forEach((part: Change) => { + const count = part.count ?? 0 + if (part.removed) { + if (part.value !== '\n') { + textDiff.push({ + type: 'deletion', + originalText: part.value, + range: new vscode.Range(startLine, 0, startLine + count, 0), + }) + } + } else if (part.added) { + if (part.value !== '\n') { + // The partial response sometimes doesn't have the correct ending newline character (\n), so we ensure that every insertion respects the code formatting. + if (isPartialDiff && !part.value.endsWith('\n')) { + part.value += '\n' + } + textDiff.push({ + type: 'insertion', + replacementText: part.value, + range: new vscode.Range(startLine, 0, startLine + count, 0), + }) + } + } + startLine += count + }) + inlineTask.diff = textDiff + return textDiff +} + +export function adjustTextDiffForEditing(textDiff: TextDiff[]): TextDiff[] { + let linesAdded = 0 + const adjustedDiff: TextDiff[] = [] + + for (const edit of textDiff) { + const { range, type } = edit + const { start, end } = range + const linesChanged = end.line - start.line + + const adjustedRange = new vscode.Range( + new vscode.Position(start.line - linesAdded, start.character), + new vscode.Position(end.line - linesAdded, end.character) + ) + + adjustedDiff.push({ + ...edit, + range: adjustedRange, + }) + + if (type === 'insertion') { + linesAdded += linesChanged + } + } + + return adjustedDiff +} + +export function getDiffBlocks(inlineTask: InlineTask): vscode.Range[] { + const diff = inlineTask.diff + + if (!diff || diff.length === 0) { + return [] + } + + const diffBlocks: vscode.Range[] = [] + let currentRange: vscode.Range | undefined + + for (const change of diff) { + const { range } = change + if (!currentRange || range.start.line !== currentRange.end.line) { + currentRange = range + diffBlocks.push(range) + } else { + currentRange = new vscode.Range(currentRange.start, range.end) + diffBlocks[diffBlocks.length - 1] = currentRange + } + } + + return diffBlocks +} + +function getLeadingWhitespace(str: string): string { + const match = str.match(/^\s*/) + return match ? match[0] : '' +} + +function getTrailingWhitespace(str: string): string { + const match = str.match(/\s*$/) + return match ? match[0] : '' +} diff --git a/packages/amazonq/src/inlineChat/output/responseTransformer.ts b/packages/amazonq/src/inlineChat/output/responseTransformer.ts new file mode 100644 index 00000000000..b965ced519e --- /dev/null +++ b/packages/amazonq/src/inlineChat/output/responseTransformer.ts @@ -0,0 +1,55 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getLogger } from 'aws-core-vscode/shared' +import { decode } from 'he' +import { InlineTask } from '../controller/inlineTask' + +/** + * Transforms the response from the INLINE_CHAT GenerateAssistantResponse call. + * + * @param response - The raw response string from GenerateAssistantResponse. + * @param inlineTask - The inline task object containing information about the current task. + * @param isWholeResponse - A boolean indicating whether this is a complete response or a partial one. + * @returns The decoded response string, or undefined if an error occurs. + */ +export function responseTransformer( + response: string, + inlineTask: InlineTask, + isWholeResponse: boolean +): string | undefined { + try { + const decodedResponse = decode(response) + if (!isWholeResponse) { + const [partialSelectedCode, right] = extractPartialCode(decodedResponse, inlineTask) + inlineTask.partialSelectedText = partialSelectedCode + inlineTask.partialSelectedTextRight = right + return decodedResponse + } else { + return decodedResponse + } + } catch (err) { + getLogger().error('An unknown error occurred: %s', (err as Error).message) + return undefined + } +} + +/** + * This function is used to handle partial responses in inline tasks. It divides + * the selected text into two parts: + * 1. The "left" part, which contains the same number of lines as the response. + * 2. The "right" part, which contains the remaining lines. + * + * @param response - The response string from the assistant. + * @param inlineTask - The inline task object containing the full selected text. + * @returns A tuple with two strings: [leftPart, rightPart]. + */ +function extractPartialCode(response: string, inlineTask: InlineTask): [string, string] { + const lineCount = response.split('\n').length + const splitLines = inlineTask.selectedText.split('\n') + const left = splitLines.slice(0, lineCount).join('\n') + const right = splitLines.slice(lineCount).join('\n') + return [left, right] +} diff --git a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts new file mode 100644 index 00000000000..aad41b3cd1b --- /dev/null +++ b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts @@ -0,0 +1,181 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { + CodeWhispererStreamingServiceException, + GenerateAssistantResponseCommandOutput, +} from '@amzn/codewhisperer-streaming' +import { AuthUtil, getSelectedCustomization } from 'aws-core-vscode/codewhisperer' +import { + ChatSessionStorage, + ChatTriggerType, + EditorContextExtractor, + PromptMessage, + TriggerEventsStorage, + TriggerPayload, + triggerPayloadToChatRequest, + UserIntentRecognizer, +} from 'aws-core-vscode/codewhispererChat' +import { AwsClientResponseError, getLogger, isAwsError, ToolkitError } from 'aws-core-vscode/shared' +import { randomUUID } from 'crypto' +import { codeWhispererClient } from 'aws-core-vscode/codewhisperer' +import type { InlineChatEvent } from 'aws-core-vscode/codewhisperer' +import { InlineTask } from '../controller/inlineTask' +import { extractAuthFollowUp } from 'aws-core-vscode/amazonq' + +export class InlineChatProvider { + private readonly editorContextExtractor: EditorContextExtractor + private readonly userIntentRecognizer: UserIntentRecognizer + private readonly sessionStorage: ChatSessionStorage + private readonly triggerEventsStorage: TriggerEventsStorage + private errorEmitter = new vscode.EventEmitter() + public onErrorOccured = this.errorEmitter.event + + public constructor() { + this.editorContextExtractor = new EditorContextExtractor() + this.userIntentRecognizer = new UserIntentRecognizer() + this.sessionStorage = new ChatSessionStorage() + this.triggerEventsStorage = new TriggerEventsStorage() + } + + public async processPromptMessage(message: PromptMessage) { + return this.editorContextExtractor + .extractContextForTrigger('ChatMessage') + .then((context) => { + const triggerID = randomUUID() + this.triggerEventsStorage.addTriggerEvent({ + id: triggerID, + tabID: message.tabID, + message: message.message, + type: 'inline_chat', + context, + }) + return this.generateResponse( + { + message: message.message, + trigger: ChatTriggerType.InlineChatMessage, + query: message.message, + codeSelection: context?.focusAreaContext?.selectionInsideExtendedCodeBlock, + fileText: context?.focusAreaContext?.extendedCodeBlock, + fileLanguage: context?.activeFileContext?.fileLanguage, + filePath: context?.activeFileContext?.filePath, + matchPolicy: context?.activeFileContext?.matchPolicy, + codeQuery: context?.focusAreaContext?.names, + userIntent: this.userIntentRecognizer.getFromPromptChatMessage(message), + customization: getSelectedCustomization(), + }, + triggerID + ) + }) + .catch((e) => { + this.processException(e, message.tabID) + }) + } + + private async generateResponse( + triggerPayload: TriggerPayload & { projectContextQueryLatencyMs?: number }, + triggerID: string + ) { + const triggerEvent = this.triggerEventsStorage.getTriggerEvent(triggerID) + if (triggerEvent === undefined) { + return + } + + if (triggerEvent.tabID === 'no-available-tabs') { + return + } + + if (triggerEvent.tabID === undefined) { + setTimeout(() => { + this.generateResponse(triggerPayload, triggerID).catch((e) => { + getLogger().error('generateResponse failed: %s', (e as Error).message) + }) + }, 20) + return + } + + const tabID = triggerEvent.tabID + + const credentialsState = await AuthUtil.instance.getChatAuthState() + if ( + !(credentialsState.codewhispererChat === 'connected' && credentialsState.codewhispererCore === 'connected') + ) { + const { message } = extractAuthFollowUp(credentialsState) + this.errorEmitter.fire() + throw new ToolkitError(message) + } + triggerPayload.useRelevantDocuments = false + + const request = triggerPayloadToChatRequest(triggerPayload) + const session = this.sessionStorage.getSession(tabID) + getLogger().info(`request from tab: ${tabID} conversationID: ${session.sessionIdentifier} request: %O`, request) + + let response: GenerateAssistantResponseCommandOutput | undefined = undefined + session.createNewTokenSource() + try { + response = await session.chatSso(request) + getLogger().info( + `response to tab: ${tabID} conversationID: ${session.sessionIdentifier} requestID: ${response.$metadata.requestId} metadata: %O`, + response.$metadata + ) + } catch (e: any) { + this.processException(e, tabID) + } + + return response + } + + private processException(e: any, tabID: string) { + let errorMessage: string | undefined + let requestID: string | undefined + if (typeof e === 'string') { + errorMessage = e.toUpperCase() + } else if (e instanceof SyntaxError) { + // Workaround to handle case when LB returns web-page with error and our client doesn't return proper exception + errorMessage = AwsClientResponseError.tryExtractReasonFromSyntaxError(e) + } else if (e instanceof CodeWhispererStreamingServiceException) { + errorMessage = e.message + requestID = e.$metadata.requestId + } else if (e instanceof Error) { + errorMessage = e.message + } + + this.errorEmitter.fire() + this.sessionStorage.deleteSession(tabID) + + throw ToolkitError.chain(e, errorMessage ?? 'Failed to get response', { + details: { + tabID, + requestID, + }, + }) + } + + public sendTelemetryEvent(inlineChatEvent: InlineChatEvent, currentTask?: InlineTask) { + codeWhispererClient + .sendTelemetryEvent({ + telemetryEvent: { + inlineChatEvent: { + ...inlineChatEvent, + ...(currentTask?.inlineChatEventBase() ?? {}), + }, + }, + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + + getLogger().debug( + `Failed to sendTelemetryEvent to CodeWhisperer, requestId: ${ + requestId ?? '' + }, message: ${error.message}` + ) + }) + } +} diff --git a/packages/amazonq/test/e2e/amazonq/doc.test.ts b/packages/amazonq/test/e2e/amazonq/doc.test.ts new file mode 100644 index 00000000000..78322b63ab0 --- /dev/null +++ b/packages/amazonq/test/e2e/amazonq/doc.test.ts @@ -0,0 +1,113 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import { qTestingFramework } from './framework/framework' +import sinon from 'sinon' +import { registerAuthHook, using } from 'aws-core-vscode/test' +import { loginToIdC } from './utils/setup' +import { Messenger } from './framework/messenger' +import { FollowUpTypes } from 'aws-core-vscode/amazonq' +import { i18n } from 'aws-core-vscode/shared' +import { docGenerationProgressMessage, DocGenerationStep, Mode } from 'aws-core-vscode/amazonqDoc' + +describe('Amazon Q Doc', async function () { + let framework: qTestingFramework + let tab: Messenger + + before(async function () { + /** + * The tests are getting throttled, only run them on stable for now + * + * TODO: Re-enable for all versions once the backend can handle them + */ + const testVersion = process.env['VSCODE_TEST_VERSION'] + if (testVersion && testVersion !== 'stable') { + this.skip() + } + + await using(registerAuthHook('amazonq-test-account'), async () => { + await loginToIdC() + }) + }) + + beforeEach(() => { + registerAuthHook('amazonq-test-account') + framework = new qTestingFramework('doc', true, []) + tab = framework.createTab() + }) + + afterEach(() => { + framework.removeTab(tab.tabID) + framework.dispose() + sinon.restore() + }) + + describe('Quick action availability', () => { + it('Shows /doc when doc generation is enabled', async () => { + const command = tab.findCommand('/doc') + if (!command.length) { + assert.fail('Could not find command') + } + + if (command.length > 1) { + assert.fail('Found too many commands with the name /doc') + } + }) + + it('Does NOT show /doc when doc generation is NOT enabled', () => { + // The beforeEach registers a framework which accepts requests. If we don't dispose before building a new one we have duplicate messages + framework.dispose() + framework = new qTestingFramework('doc', false, []) + const tab = framework.createTab() + const command = tab.findCommand('/doc') + if (command.length > 0) { + assert.fail('Found command when it should not have been found') + } + }) + }) + + describe('/doc entry', () => { + beforeEach(async function () { + tab.addChatMessage({ command: '/doc' }) + await tab.waitForChatFinishesLoading() + }) + + it('Checks for initial follow ups', async () => { + await tab.waitForButtons([FollowUpTypes.CreateDocumentation, FollowUpTypes.UpdateDocumentation]) + }) + }) + + describe('Creates a README', () => { + beforeEach(async function () { + tab.addChatMessage({ command: '/doc' }) + await tab.waitForChatFinishesLoading() + }) + + it('Creates a README for root folder', async () => { + await tab.waitForButtons([FollowUpTypes.CreateDocumentation]) + + tab.clickButton(FollowUpTypes.CreateDocumentation) + + await tab.waitForText(i18n('AWS.amazonq.doc.answer.createReadme')) + + await tab.waitForButtons([FollowUpTypes.ProceedFolderSelection]) + + tab.clickButton(FollowUpTypes.ProceedFolderSelection) + + await tab.waitForText(docGenerationProgressMessage(DocGenerationStep.SUMMARIZING_FILES, Mode.CREATE)) + + await tab.waitForText( + `${i18n('AWS.amazonq.doc.answer.readmeCreated')} ${i18n('AWS.amazonq.doc.answer.codeResult')}` + ) + + await tab.waitForButtons([ + FollowUpTypes.AcceptChanges, + FollowUpTypes.MakeChanges, + FollowUpTypes.RejectChanges, + ]) + }) + }) +}) diff --git a/packages/amazonq/test/e2e/amazonq/featureDev.test.ts b/packages/amazonq/test/e2e/amazonq/featureDev.test.ts index 861ce35fb2b..cc1670ced8f 100644 --- a/packages/amazonq/test/e2e/amazonq/featureDev.test.ts +++ b/packages/amazonq/test/e2e/amazonq/featureDev.test.ts @@ -9,7 +9,8 @@ import sinon from 'sinon' import { registerAuthHook, using } from 'aws-core-vscode/test' import { loginToIdC } from './utils/setup' import { Messenger } from './framework/messenger' -import { FollowUpTypes, examples } from 'aws-core-vscode/amazonqFeatureDev' +import { examples } from 'aws-core-vscode/amazonqFeatureDev' +import { FollowUpTypes } from 'aws-core-vscode/amazonq' import { sleep } from 'aws-core-vscode/shared' describe('Amazon Q Feature Dev', function () { @@ -17,25 +18,15 @@ describe('Amazon Q Feature Dev', function () { let tab: Messenger const prompt = 'Add blank.txt file with empty content' - const codegenApproachPrompt = prompt + ' and add a readme that describes the changes' + const codegenApproachPrompt = `${prompt} and add a readme that describes the changes` + const fileLevelAcceptPrompt = `${prompt} and add a license, and a contributing file` const tooManyRequestsWaitTime = 100000 - function waitForButtons(buttons: FollowUpTypes[]) { - return tab.waitForEvent(() => { - return buttons.every((value) => tab.hasButton(value)) - }) - } - async function waitForText(text: string) { - await tab.waitForEvent( - () => { - return tab.getChatItems().some((chatItem) => chatItem.body === text) - }, - { - waitIntervalInMs: 250, - waitTimeoutInMs: 2000, - } - ) + await tab.waitForText(text, { + waitIntervalInMs: 250, + waitTimeoutInMs: 2000, + }) } async function iterate(prompt: string) { @@ -50,6 +41,14 @@ describe('Amazon Q Feature Dev', function () { ) } + async function clickActionButton(filePath: string, actionName: string) { + tab.clickFileActionButton(filePath, actionName) + await tab.waitForEvent(() => !tab.hasAction(filePath, actionName), { + waitIntervalInMs: 500, + waitTimeoutInMs: 600000, + }) + } + /** * Wait for the original request to finish. * If the response has a retry button or encountered a guardrails error, continue retrying @@ -110,7 +109,7 @@ describe('Amazon Q Feature Dev', function () { beforeEach(() => { registerAuthHook('amazonq-test-account') - framework = new qTestingFramework('featuredev', true) + framework = new qTestingFramework('featuredev', true, []) tab = framework.createTab() }) @@ -135,7 +134,7 @@ describe('Amazon Q Feature Dev', function () { it('Does NOT show /dev when feature dev is NOT enabled', () => { // The beforeEach registers a framework which accepts requests. If we don't dispose before building a new one we have duplicate messages framework.dispose() - framework = new qTestingFramework('featuredev', false) + framework = new qTestingFramework('featuredev', false, []) const tab = framework.createTab() const command = tab.findCommand('/dev') if (command.length > 0) { @@ -191,12 +190,12 @@ describe('Amazon Q Feature Dev', function () { it('Clicks accept code and click new task', async () => { await retryIfRequired(async () => { await Promise.any([ - waitForButtons([FollowUpTypes.InsertCode, FollowUpTypes.ProvideFeedbackAndRegenerateCode]), - waitForButtons([FollowUpTypes.Retry]), + tab.waitForButtons([FollowUpTypes.InsertCode, FollowUpTypes.ProvideFeedbackAndRegenerateCode]), + tab.waitForButtons([FollowUpTypes.Retry]), ]) }) tab.clickButton(FollowUpTypes.InsertCode) - await waitForButtons([FollowUpTypes.NewTask, FollowUpTypes.CloseSession]) + await tab.waitForButtons([FollowUpTypes.NewTask, FollowUpTypes.CloseSession]) tab.clickButton(FollowUpTypes.NewTask) await waitForText('What new task would you like to work on?') assert.deepStrictEqual(tab.getChatItems().pop()?.body, 'What new task would you like to work on?') @@ -205,15 +204,131 @@ describe('Amazon Q Feature Dev', function () { it('Iterates on codegen', async () => { await retryIfRequired(async () => { await Promise.any([ - waitForButtons([FollowUpTypes.InsertCode, FollowUpTypes.ProvideFeedbackAndRegenerateCode]), - waitForButtons([FollowUpTypes.Retry]), + tab.waitForButtons([FollowUpTypes.InsertCode, FollowUpTypes.ProvideFeedbackAndRegenerateCode]), + tab.waitForButtons([FollowUpTypes.Retry]), ]) }) tab.clickButton(FollowUpTypes.ProvideFeedbackAndRegenerateCode) await tab.waitForChatFinishesLoading() await iterate(codegenApproachPrompt) tab.clickButton(FollowUpTypes.InsertCode) - await waitForButtons([FollowUpTypes.NewTask, FollowUpTypes.CloseSession]) + await tab.waitForButtons([FollowUpTypes.NewTask, FollowUpTypes.CloseSession]) + }) + }) + + describe('file-level accepts', async () => { + beforeEach(async function () { + tab.addChatMessage({ command: '/dev', prompt: fileLevelAcceptPrompt }) + await retryIfRequired( + async () => { + await tab.waitForChatFinishesLoading() + }, + () => { + tab.addChatMessage({ prompt }) + } + ) + await retryIfRequired(async () => { + await Promise.any([ + tab.waitForButtons([FollowUpTypes.InsertCode, FollowUpTypes.ProvideFeedbackAndRegenerateCode]), + tab.waitForButtons([FollowUpTypes.Retry]), + ]) + }) + }) + + describe('fileList', async () => { + it('has both accept-change and reject-change action buttons for file', async () => { + const filePath = tab.getFilePaths()[0] + assert.ok(tab.getActionsByFilePath(filePath).length === 2) + assert.ok(tab.hasAction(filePath, 'accept-change')) + assert.ok(tab.hasAction(filePath, 'reject-change')) + }) + + it('has only revert-rejection action button for rejected file', async () => { + const filePath = tab.getFilePaths()[0] + await clickActionButton(filePath, 'reject-change') + + assert.ok(tab.getActionsByFilePath(filePath).length === 1) + assert.ok(tab.hasAction(filePath, 'revert-rejection')) + }) + + it('does not have any of the action buttons for accepted file', async () => { + const filePath = tab.getFilePaths()[0] + await clickActionButton(filePath, 'accept-change') + + assert.ok(tab.getActionsByFilePath(filePath).length === 0) + }) + + it('disables all action buttons when new task is clicked', async () => { + tab.clickButton(FollowUpTypes.InsertCode) + await tab.waitForButtons([FollowUpTypes.NewTask, FollowUpTypes.CloseSession]) + tab.clickButton(FollowUpTypes.NewTask) + await waitForText('What new task would you like to work on?') + + const filePaths = tab.getFilePaths() + for (const filePath of filePaths) { + assert.ok(tab.getActionsByFilePath(filePath).length === 0) + } + }) + + it('disables all action buttons when close session is clicked', async () => { + tab.clickButton(FollowUpTypes.InsertCode) + await tab.waitForButtons([FollowUpTypes.NewTask, FollowUpTypes.CloseSession]) + tab.clickButton(FollowUpTypes.CloseSession) + await waitForText( + "Okay, I've ended this chat session. You can open a new tab to chat or start another workflow." + ) + + const filePaths = tab.getFilePaths() + for (const filePath of filePaths) { + assert.ok(tab.getActionsByFilePath(filePath).length === 0) + } + }) + }) + + describe('accept button', async () => { + describe('button text', async () => { + it('shows "Accept all changes" when no files are accepted or rejected, and "Accept remaining changes" otherwise', async () => { + let insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept all changes') + + const filePath = tab.getFilePaths()[0] + await clickActionButton(filePath, 'reject-change') + + insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept remaining changes') + + await clickActionButton(filePath, 'revert-rejection') + + insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept all changes') + + await clickActionButton(filePath, 'accept-change') + + insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Accept remaining changes') + }) + + it('shows "Continue" when all files are either accepted or rejected, with at least one of them rejected', async () => { + const filePaths = tab.getFilePaths() + for (const filePath of filePaths) { + await clickActionButton(filePath, 'reject-change') + } + + const insertCodeButton = tab.getFollowUpButton(FollowUpTypes.InsertCode) + assert.ok(insertCodeButton.pillText === 'Continue') + }) + }) + + it('disappears and automatically moves on to the next step when all changes are accepted', async () => { + const filePaths = tab.getFilePaths() + for (const filePath of filePaths) { + await clickActionButton(filePath, 'accept-change') + } + await tab.waitForButtons([FollowUpTypes.NewTask, FollowUpTypes.CloseSession]) + + assert.ok(tab.hasButton(FollowUpTypes.InsertCode) === false) + assert.ok(tab.hasButton(FollowUpTypes.ProvideFeedbackAndRegenerateCode) === false) + }) }) }) }) diff --git a/packages/amazonq/test/e2e/amazonq/framework/framework.ts b/packages/amazonq/test/e2e/amazonq/framework/framework.ts index 8c9e89c49f8..b65e8b184f7 100644 --- a/packages/amazonq/test/e2e/amazonq/framework/framework.ts +++ b/packages/amazonq/test/e2e/amazonq/framework/framework.ts @@ -12,6 +12,7 @@ import * as vscode from 'vscode' import { MynahUI, MynahUIProps } from '@aws/mynah-ui' import { DefaultAmazonQAppInitContext, TabType, createMynahUI } from 'aws-core-vscode/amazonq' import { Messenger, MessengerOptions } from './messenger' +import { FeatureContext } from 'aws-core-vscode/shared' /** * Abstraction over Amazon Q to make e2e testing easier @@ -23,7 +24,7 @@ export class qTestingFramework { lastEventId: string = '' - constructor(featureName: TabType, amazonQEnabled: boolean) { + constructor(featureName: TabType, amazonQEnabled: boolean, featureConfigsSerialized: [string, FeatureContext][]) { /** * Instantiate the UI and override the postMessage to publish using the app message * publishers directly. @@ -42,7 +43,8 @@ export class qTestingFramework { appMessagePublisher.publish(message) }, }, - amazonQEnabled + amazonQEnabled, + featureConfigsSerialized ) this.mynahUI = ui.mynahUI this.mynahUIProps = (this.mynahUI as any).props diff --git a/packages/amazonq/test/e2e/amazonq/framework/jsdomInjector.ts b/packages/amazonq/test/e2e/amazonq/framework/jsdomInjector.ts index ef92521b1d9..ce8309c1039 100644 --- a/packages/amazonq/test/e2e/amazonq/framework/jsdomInjector.ts +++ b/packages/amazonq/test/e2e/amazonq/framework/jsdomInjector.ts @@ -3,15 +3,24 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { JSDOM } from 'jsdom' +import { JSDOM, VirtualConsole } from 'jsdom' +/** + * JSDOM is used to help hoist MynahUI to running in a node environment vs in the browser (which is what it's made for) + */ export function injectJSDOM() { - /** - * JSDOM is used to help hoist MynahUI to running in a node environment vs in the browser (which is what it's made for) - */ + const virtualConsole = new VirtualConsole() + virtualConsole.on('error', (error) => { + // JSDOM can't load scss from mynah UI, just skip it + if (!error.includes('Could not parse CSS stylesheet')) { + console.error(error) + } + }) + const dom = new JSDOM(undefined, { pretendToBeVisual: true, includeNodeLocations: true, + virtualConsole, }) global.window = dom.window as unknown as Window & typeof globalThis global.document = dom.window.document @@ -34,5 +43,5 @@ export function injectJSDOM() { }) // jsdom doesn't have support for structuredClone. See https://github.com/jsdom/jsdom/issues/3363 - global.structuredClone = (val) => JSON.parse(JSON.stringify(val)) + global.structuredClone = (val: any) => JSON.parse(JSON.stringify(val)) } diff --git a/packages/amazonq/test/e2e/amazonq/framework/messenger.ts b/packages/amazonq/test/e2e/amazonq/framework/messenger.ts index ce612bd41c5..80e68f7481e 100644 --- a/packages/amazonq/test/e2e/amazonq/framework/messenger.ts +++ b/packages/amazonq/test/e2e/amazonq/framework/messenger.ts @@ -6,7 +6,7 @@ import assert from 'assert' import { MynahUI, MynahUIProps, MynahUIDataModel } from '@aws/mynah-ui' import { waitUntil } from 'aws-core-vscode/shared' -import { FollowUpTypes } from 'aws-core-vscode/amazonqFeatureDev' +import { FollowUpTypes } from 'aws-core-vscode/amazonq' export interface MessengerOptions { waitIntervalInMs?: number @@ -52,13 +52,29 @@ export class Messenger { const lastChatItem = this.getChatItems().pop() const option = lastChatItem?.followUp?.options?.filter((option) => option.type === type) - if (!option || option.length > 1) { + if (!option?.length || option.length > 1) { assert.fail('Could not find follow up option') } this.mynahUIProps.onFollowUpClicked(this.tabID, lastChatItem?.messageId ?? '', option[0]) } + clickCustomFormButton(action: { id: string; text?: string; formItemValues?: Record }) { + if (!this.mynahUIProps.onCustomFormAction) { + assert.fail('onCustomFormAction must be defined to use it in the tests') + } + + this.mynahUIProps.onCustomFormAction(this.tabID, action) + } + + clickFileActionButton(filePath: string, actionName: string) { + if (!this.mynahUIProps.onFileActionClick) { + assert.fail('onFileActionClick must be defined to use it in the tests') + } + + this.mynahUIProps.onFileActionClick(this.tabID, this.getFileListMessageId(), filePath, actionName) + } + findCommand(command: string) { return this.getCommands() .map((groups) => groups.commands) @@ -78,6 +94,52 @@ export class Messenger { return this.getStore().promptInputPlaceholder } + getFollowUpButton(type: FollowUpTypes) { + const followUpButton = this.getChatItems() + .pop() + ?.followUp?.options?.find((action) => action.type === type) + if (!followUpButton) { + assert.fail(`Could not find follow up button with type ${type}`) + } + return followUpButton + } + + getFileList() { + const chatItems = this.getChatItems() + const fileList = chatItems.find((item) => 'fileList' in item) + if (!fileList) { + assert.fail('Could not find file list') + } + return fileList + } + + getFileListMessageId() { + const fileList = this.getFileList() + const messageId = fileList?.messageId + if (!messageId) { + assert.fail('Could not find file list message id') + } + return messageId + } + + getFilePaths() { + const fileList = this.getFileList() + const filePaths = fileList?.fileList?.filePaths + if (!filePaths) { + assert.fail('Could not find file paths') + } + if (filePaths.length === 0) { + assert.fail('File paths list is empty') + } + return filePaths + } + + getActionsByFilePath(filePath: string) { + const fileList = this.getFileList() + const actions = fileList?.fileList?.actions + return actions?.[filePath] ?? [] + } + hasButton(type: FollowUpTypes) { return ( this.getChatItems() @@ -87,17 +149,27 @@ export class Messenger { ) } + hasAction(filePath: string, actionName: string) { + return this.getActionsByFilePath(filePath).some((action) => action.name === actionName) + } + + async waitForText(text: string, waitOverrides?: MessengerOptions) { + await this.waitForEvent(() => { + return this.getChatItems().some((chatItem) => chatItem.body === text) + }, waitOverrides) + } + + async waitForButtons(buttons: FollowUpTypes[]) { + return this.waitForEvent(() => { + return buttons.every((value) => this.hasButton(value)) + }) + } + async waitForChatFinishesLoading() { return this.waitForEvent(() => this.getStore().loadingChat === false || this.hasButton(FollowUpTypes.Retry)) } - async waitForEvent( - event: () => boolean, - waitOverrides?: { - waitIntervalInMs: number - waitTimeoutInMs: number - } - ) { + async waitForEvent(event: () => boolean, waitOverrides?: MessengerOptions) { /** * Wait until the chat has finished loading. This happens when a backend request * has finished and responded in the chat @@ -115,7 +187,9 @@ export class Messenger { // Do another check just in case the waitUntil time'd out if (!event()) { - assert.fail(`Event has not finished loading in: ${this.waitTimeoutInMs} ms`) + assert.fail( + `Event has not finished loading in: ${waitOverrides ? waitOverrides.waitTimeoutInMs : this.waitTimeoutInMs} ms` + ) } } diff --git a/packages/amazonq/test/e2e/amazonq/transformByQ.test.ts b/packages/amazonq/test/e2e/amazonq/transformByQ.test.ts new file mode 100644 index 00000000000..69fc905c985 --- /dev/null +++ b/packages/amazonq/test/e2e/amazonq/transformByQ.test.ts @@ -0,0 +1,347 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import { qTestingFramework } from './framework/framework' +import sinon from 'sinon' +import { Messenger } from './framework/messenger' +import { JDKVersion, TransformationType, transformByQState } from 'aws-core-vscode/codewhisperer' +import { GumbyController, setMaven, startTransformByQ, TabsStorage } from 'aws-core-vscode/amazonqGumby' +import { using, registerAuthHook, TestFolder } from 'aws-core-vscode/test' +import { loginToIdC } from './utils/setup' +import { fs } from 'aws-core-vscode/shared' +import path from 'path' + +describe('Amazon Q Code Transformation', function () { + let framework: qTestingFramework + let tab: Messenger + + before(async function () { + await using(registerAuthHook('amazonq-test-account'), async () => { + await loginToIdC() + }) + }) + + beforeEach(() => { + registerAuthHook('amazonq-test-account') + framework = new qTestingFramework('gumby', true, []) + tab = framework.createTab() + }) + + afterEach(() => { + framework.removeTab(tab.tabID) + framework.dispose() + sinon.restore() + }) + + describe('Quick action availability', () => { + it('Can invoke /transform when QCT is enabled', async () => { + const command = tab.findCommand('/transform') + if (!command) { + assert.fail('Could not find command') + } + + if (command.length > 1) { + assert.fail('Found too many commands with the name /transform') + } + }) + + it('CANNOT invoke /transform when QCT is NOT enabled', () => { + framework.dispose() + framework = new qTestingFramework('gumby', false, []) + const tab = framework.createTab() + const command = tab.findCommand('/transform') + if (command.length > 0) { + assert.fail('Found command when it should not have been found') + } + }) + }) + + describe('Starting a transformation from chat', () => { + it('Can click through all user input forms for a Java upgrade', async () => { + sinon.stub(startTransformByQ, 'getValidSQLConversionCandidateProjects').resolves([]) + sinon.stub(GumbyController.prototype, 'validateLanguageUpgradeProjects' as keyof GumbyController).resolves([ + { + name: 'qct-sample-java-8-app-main', + path: '/Users/alias/Desktop/qct-sample-java-8-app-main', + JDKVersion: JDKVersion.JDK8, + }, + ]) + + tab.addChatMessage({ command: '/transform' }) + + // wait for /transform to respond with some intro messages and the first user input form + await tab.waitForEvent(() => tab.getChatItems().length > 3, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const projectForm = tab.getChatItems().pop() + assert.strictEqual(projectForm?.formItems?.[0]?.id ?? undefined, 'GumbyTransformLanguageUpgradeProjectForm') + + const projectFormItemValues = { + GumbyTransformLanguageUpgradeProjectForm: '/Users/alias/Desktop/qct-sample-java-8-app-main', + GumbyTransformJdkFromForm: '8', + GumbyTransformJdkToForm: '17', + } + const projectFormValues: Record = { ...projectFormItemValues } + // TODO: instead of stubbing, can we create a tab in qTestingFramework with tabType passed in? + // Mynah-UI updates tab type like this: this.tabsStorage.updateTabTypeFromUnknown(affectedTabId, 'gumby') + sinon + .stub(TabsStorage.prototype, 'getTab') + .returns({ id: tab.tabID, status: 'free', type: 'gumby', isSelected: true }) + tab.clickCustomFormButton({ + id: 'gumbyLanguageUpgradeTransformFormConfirm', + text: 'Confirm', + formItemValues: projectFormValues, + }) + + // 3 additional chat messages (including message with 2nd form) get sent after 1st form submitted; wait for all of them + await tab.waitForEvent(() => tab.getChatItems().length > 6, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const skipTestsForm = tab.getChatItems().pop() + assert.strictEqual(skipTestsForm?.formItems?.[0]?.id ?? undefined, 'GumbyTransformSkipTestsForm') + + const skipTestsFormItemValues = { + GumbyTransformSkipTestsForm: 'Run unit tests', + } + const skipTestsFormValues: Record = { ...skipTestsFormItemValues } + tab.clickCustomFormButton({ + id: 'gumbyTransformSkipTestsFormConfirm', + text: 'Confirm', + formItemValues: skipTestsFormValues, + }) + + // 3 additional chat messages (including message with 3rd form) get sent after 2nd form submitted; wait for all of them + await tab.waitForEvent(() => tab.getChatItems().length > 9, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const multipleDiffsForm = tab.getChatItems().pop() + assert.strictEqual( + multipleDiffsForm?.formItems?.[0]?.id ?? undefined, + 'GumbyTransformOneOrMultipleDiffsForm' + ) + + const oneOrMultipleDiffsFormItemValues = { + GumbyTransformOneOrMultipleDiffsForm: 'One diff', + } + const oneOrMultipleDiffsFormValues: Record = { ...oneOrMultipleDiffsFormItemValues } + tab.clickCustomFormButton({ + id: 'gumbyTransformOneOrMultipleDiffsFormConfirm', + text: 'Confirm', + formItemValues: oneOrMultipleDiffsFormValues, + }) + + // 2 additional chat messages (including message with 4th form) get sent after 3rd form submitted; wait for both of them + await tab.waitForEvent(() => tab.getChatItems().length > 11, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const jdkPathPrompt = tab.getChatItems().pop() + assert.strictEqual(jdkPathPrompt?.body?.includes('Enter the path to JDK'), true) + + // 2 additional chat messages get sent after 4th form submitted; wait for both of them + tab.addChatMessage({ prompt: '/dummy/path/to/jdk8' }) + await tab.waitForEvent(() => tab.getChatItems().length > 13, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const jdkPathResponse = tab.getChatItems().pop() + // this 'Sorry' message is OK - just making sure that the UI components are working correctly + assert.strictEqual(jdkPathResponse?.body?.includes("Sorry, I couldn't locate your Java installation"), true) + }) + + it('Can provide metadata file for a SQL conversion', async () => { + sinon.stub(startTransformByQ, 'getValidSQLConversionCandidateProjects').resolves([ + { + name: 'OracleExample', + path: '/Users/alias/Desktop/OracleExample', + JDKVersion: JDKVersion.JDK17, + }, + ]) + sinon.stub(startTransformByQ, 'getValidLanguageUpgradeCandidateProjects').resolves([]) + sinon.stub(GumbyController.prototype, 'validateSQLConversionProjects' as keyof GumbyController).resolves([ + { + name: 'OracleExample', + path: '/Users/alias/Desktop/OracleExample', + JDKVersion: JDKVersion.JDK17, + }, + ]) + + tab.addChatMessage({ command: '/transform' }) + + // wait for /transform to respond with some intro messages and the first user input message + await tab.waitForEvent(() => tab.getChatItems().length > 3, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const selectMetadataMessage = tab.getChatItems().pop() + assert.strictEqual( + selectMetadataMessage?.body?.includes('I can convert the embedded SQL') ?? undefined, + true + ) + + // verify that we processed the metadata file + const processMetadataFileStub = sinon.stub( + GumbyController.prototype, + 'processMetadataFile' as keyof GumbyController + ) + tab.clickCustomFormButton({ + id: 'gumbySQLConversionMetadataTransformFormConfirm', + text: 'Select metadata file', + }) + sinon.assert.calledOnce(processMetadataFileStub) + }) + + it('Can choose "language upgrade" when eligible for a Java upgrade AND SQL conversion', async () => { + sinon.stub(startTransformByQ, 'getValidSQLConversionCandidateProjects').resolves([ + { + name: 'OracleExample', + path: '/Users/alias/Desktop/OracleExample', + JDKVersion: JDKVersion.JDK17, + }, + ]) + sinon.stub(startTransformByQ, 'getValidLanguageUpgradeCandidateProjects').resolves([ + { + name: 'qct-sample-java-8-app-main', + path: '/Users/alias/Desktop/qct-sample-java-8-app-main', + JDKVersion: JDKVersion.JDK8, + }, + ]) + + tab.addChatMessage({ command: '/transform' }) + + // wait for /transform to respond with some intro messages and a prompt asking user what they want to do + await tab.waitForEvent(() => tab.getChatItems().length > 2, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const prompt = tab.getChatItems().pop() + assert.strictEqual( + prompt?.body?.includes('You can enter "language upgrade" or "sql conversion"') ?? undefined, + true + ) + + // 3 additional chat messages get sent after user enters a choice; wait for all of them + tab.addChatMessage({ prompt: 'language upgrade' }) + await tab.waitForEvent(() => tab.getChatItems().length > 5, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const projectForm = tab.getChatItems().pop() + assert.strictEqual(projectForm?.formItems?.[0]?.id ?? undefined, 'GumbyTransformLanguageUpgradeProjectForm') + }) + + it('Can choose "sql conversion" when eligible for a Java upgrade AND SQL conversion', async () => { + sinon.stub(startTransformByQ, 'getValidSQLConversionCandidateProjects').resolves([ + { + name: 'OracleExample', + path: '/Users/alias/Desktop/OracleExample', + JDKVersion: JDKVersion.JDK17, + }, + ]) + sinon.stub(startTransformByQ, 'getValidLanguageUpgradeCandidateProjects').resolves([ + { + name: 'qct-sample-java-8-app-main', + path: '/Users/alias/Desktop/qct-sample-java-8-app-main', + JDKVersion: JDKVersion.JDK8, + }, + ]) + + tab.addChatMessage({ command: '/transform' }) + + // wait for /transform to respond with some intro messages and a prompt asking user what they want to do + await tab.waitForEvent(() => tab.getChatItems().length > 2, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const prompt = tab.getChatItems().pop() + assert.strictEqual( + prompt?.body?.includes('You can enter "language upgrade" or "sql conversion"') ?? undefined, + true + ) + + // 3 additional chat messages get sent after user enters a choice; wait for all of them + tab.addChatMessage({ prompt: 'sql conversion' }) + await tab.waitForEvent(() => tab.getChatItems().length > 5, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + const selectMetadataMessage = tab.getChatItems().pop() + assert.strictEqual( + selectMetadataMessage?.body?.includes('I can convert the embedded SQL') ?? undefined, + true + ) + }) + }) + + // TODO: enable when we no longer get throttled on CreateUploadUrl and other APIs + describe.skip('Running a Java upgrade from start to finish', async function () { + let tempDir = '' + let tempFileName = '' + let tempFilePath = '' + + const javaFileContents = `public class MyApp { + public static void main(String[] args) { + Integer temp = new Integer("1234"); + } + }` + + const pomXmlContents = ` + + 4.0.0 + + com.example + basic-java-app + 1.0-SNAPSHOT + + + 1.8 + 1.8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + ` + + before(async function () { + tempDir = path.join((await TestFolder.create()).path, 'qct-java-upgrade-test') + tempFileName = 'MyApp.java' + tempFilePath = path.join(tempDir, tempFileName) + await fs.writeFile(tempFilePath, javaFileContents) + tempFileName = 'pom.xml' + tempFilePath = path.join(tempDir, tempFileName) + await fs.writeFile(tempFilePath, pomXmlContents) + }) + + after(async function () { + await fs.delete(tempDir, { recursive: true }) + }) + + it('WHEN transforming a Java 8 project E2E THEN job is successful', async function () { + transformByQState.setTransformationType(TransformationType.LANGUAGE_UPGRADE) + await setMaven() + await startTransformByQ.processLanguageUpgradeTransformFormInput(tempDir, JDKVersion.JDK8, JDKVersion.JDK17) + await startTransformByQ.startTransformByQ() + assert.strictEqual(transformByQState.getPolledJobStatus(), 'COMPLETED') + }) + }) +}) diff --git a/packages/amazonq/test/e2e/index.ts b/packages/amazonq/test/e2e/index.ts index 168d4028f57..065a2cd8bc8 100644 --- a/packages/amazonq/test/e2e/index.ts +++ b/packages/amazonq/test/e2e/index.ts @@ -7,9 +7,7 @@ import { runTests } from 'aws-core-vscode/test' import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils' export function run(): Promise { - return runTests( - process.env.TEST_DIR ?? 'test/e2e', - ['../../core/dist/src/testInteg/globalSetup.test.ts'], - VSCODE_EXTENSION_ID.amazonq - ) + return runTests(process.env.TEST_DIR ?? 'test/e2e', VSCODE_EXTENSION_ID.amazonq, [ + '../../core/dist/src/testInteg/globalSetup.test.ts', + ]) } diff --git a/packages/amazonq/test/unit/amazonq/lsp/lspClient.test.ts b/packages/amazonq/test/unit/amazonq/lsp/lspClient.test.ts index affbc20a802..0ffb6adc907 100644 --- a/packages/amazonq/test/unit/amazonq/lsp/lspClient.test.ts +++ b/packages/amazonq/test/unit/amazonq/lsp/lspClient.test.ts @@ -18,7 +18,7 @@ describe('Amazon Q LSP client', function () { }) it('encrypts payload of query ', async () => { - await lspClient.query('mock_input') + await lspClient.queryVectorIndex('mock_input') assert.ok(encryptFunc.calledOnce) assert.ok(encryptFunc.calledWith(JSON.stringify({ query: 'mock_input' }))) const value = await encryptFunc.returnValues[0] @@ -27,14 +27,15 @@ describe('Amazon Q LSP client', function () { }) it('encrypts payload of index files ', async () => { - await lspClient.indexFiles(['fileA'], 'path', false) + await lspClient.buildIndex(['fileA'], 'path', 'all') assert.ok(encryptFunc.calledOnce) assert.ok( encryptFunc.calledWith( JSON.stringify({ filePaths: ['fileA'], - rootPath: 'path', - refresh: false, + projectRoot: 'path', + config: 'all', + language: '', }) ) ) diff --git a/packages/amazonq/test/unit/amazonqFeatureDev/session/chatSessionStorage.test.ts b/packages/amazonq/test/unit/amazonqFeatureDev/session/chatSessionStorage.test.ts index 29a78c552ce..4c6073114f8 100644 --- a/packages/amazonq/test/unit/amazonqFeatureDev/session/chatSessionStorage.test.ts +++ b/packages/amazonq/test/unit/amazonqFeatureDev/session/chatSessionStorage.test.ts @@ -3,18 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ import * as assert from 'assert' - -import { Messenger, ChatSessionStorage } from 'aws-core-vscode/amazonqFeatureDev' +import { FeatureDevChatSessionStorage } from 'aws-core-vscode/amazonqFeatureDev' +import { Messenger } from 'aws-core-vscode/amazonq' import { createMessenger } from 'aws-core-vscode/test' describe('chatSession', () => { const tabID = '1234' - let chatStorage: ChatSessionStorage + let chatStorage: FeatureDevChatSessionStorage let messenger: Messenger beforeEach(() => { messenger = createMessenger() - chatStorage = new ChatSessionStorage(messenger) + chatStorage = new FeatureDevChatSessionStorage(messenger) }) it('locks getSession', async () => { diff --git a/packages/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts b/packages/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts index 2af763ecf11..a7a5d831f67 100644 --- a/packages/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts +++ b/packages/amazonq/test/unit/amazonqFeatureDev/session/session.test.ts @@ -18,7 +18,8 @@ import { sessionWriteFile, assertTelemetry, } from 'aws-core-vscode/test' -import { CurrentWsFolders, CodeGenState, FeatureDevClient, Messenger } from 'aws-core-vscode/amazonqFeatureDev' +import { CurrentWsFolders, CodeGenState, FeatureDevClient, featureDevScheme } from 'aws-core-vscode/amazonqFeatureDev' +import { Messenger } from 'aws-core-vscode/amazonq' import path from 'path' import { fs } from 'aws-core-vscode/shared' @@ -36,7 +37,7 @@ describe('session', () => { describe('preloader', () => { it('emits start chat telemetry', async () => { - const session = await createSession({ messenger, conversationID }) + const session = await createSession({ messenger, conversationID, scheme: featureDevScheme }) await session.preloader('implement twosum in typescript') @@ -63,7 +64,7 @@ describe('session', () => { const tabID = '123' const workspaceFolders = [controllerSetup.workspaceFolder] as CurrentWsFolders workspaceFolderUriFsPath = controllerSetup.workspaceFolder.uri.fsPath - uri = generateVirtualMemoryUri(uploadID, notRejectedFileName) + uri = generateVirtualMemoryUri(uploadID, notRejectedFileName, featureDevScheme) const testConfig = { conversationId: conversationID, @@ -83,22 +84,30 @@ describe('session', () => { rejected: false, virtualMemoryUri: uri, workspaceFolder: controllerSetup.workspaceFolder, + changeApplied: false, }, { zipFilePath: 'rejectedFile.js', relativePath: 'rejectedFile.js', fileContent: 'rejectedFileContent', rejected: true, - virtualMemoryUri: generateVirtualMemoryUri(uploadID, 'rejectedFile.js'), + virtualMemoryUri: generateVirtualMemoryUri(uploadID, 'rejectedFile.js', featureDevScheme), workspaceFolder: controllerSetup.workspaceFolder, + changeApplied: false, }, ], [], [], tabID, - 0 + 0, + {} ) - const session = await createSession({ messenger, sessionState: codeGenState, conversationID }) + const session = await createSession({ + messenger, + sessionState: codeGenState, + conversationID, + scheme: featureDevScheme, + }) encodedContent = new TextEncoder().encode(notRejectedFileContent) await sessionRegisterProvider(session, uri, encodedContent) return session @@ -106,6 +115,7 @@ describe('session', () => { it('only insert non rejected files', async () => { const fsSpyWriteFile = sinon.spy(fs, 'writeFile') const session = await createCodeGenState() + sinon.stub(session, 'sendLinesOfCodeAcceptedTelemetry').resolves() await sessionWriteFile(session, uri, encodedContent) await session.insertChanges() diff --git a/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts b/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts index c534b40ffde..e8ecefc171e 100644 --- a/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts +++ b/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts @@ -11,7 +11,8 @@ import { maxRepoSizeBytes, } from 'aws-core-vscode/amazonqFeatureDev' import { assertTelemetry, createTestWorkspace } from 'aws-core-vscode/test' -import { fs, AmazonqCreateUpload, Metric } from 'aws-core-vscode/shared' +import { fs, AmazonqCreateUpload } from 'aws-core-vscode/shared' +import { Span } from 'aws-core-vscode/telemetry' import sinon from 'sinon' describe('file utils', () => { @@ -28,7 +29,7 @@ describe('file utils', () => { const telemetry = new TelemetryHelper() const result = await prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, { record: () => {}, - } as unknown as Metric) + } as unknown as Span) assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true) // checksum is not the same across different test executions because some unique random folder names are generated assert.strictEqual(result.zipFileChecksum.length, 44) @@ -46,7 +47,7 @@ describe('file utils', () => { const telemetry = new TelemetryHelper() const result = await prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, { record: () => {}, - } as unknown as Metric) + } as unknown as Span) assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true) // checksum is not the same across different test executions because some unique random folder names are generated @@ -65,7 +66,7 @@ describe('file utils', () => { () => prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, { record: () => {}, - } as unknown as Metric), + } as unknown as Span), ContentLengthError ) }) diff --git a/packages/amazonq/test/unit/amazonqGumby/humanInTheLoopManager.test.ts b/packages/amazonq/test/unit/amazonqGumby/humanInTheLoopManager.test.ts index 3114713aaa4..e94838ba3c2 100644 --- a/packages/amazonq/test/unit/amazonqGumby/humanInTheLoopManager.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/humanInTheLoopManager.test.ts @@ -36,7 +36,7 @@ describe('HumanInTheLoopManager', async function () { ) const outputPathResult = path.join(outputDirectoryPath, 'pom.xml') assertEqualPaths(newPomFilePath.fsPath, outputPathResult) - const newPomFileContents = await fs.readFileAsString(newPomFilePath.path) + const newPomFileContents = await fs.readFileText(newPomFilePath.path) assert.strictEqual( stripStringWhitespace(newPomFileContents), stripStringWhitespace(` diff --git a/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json b/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json new file mode 100644 index 00000000000..5b73cdd201b --- /dev/null +++ b/packages/amazonq/test/unit/amazonqGumby/resources/files/diff.json @@ -0,0 +1,9 @@ +{ + "content": [ + { + "name": "Added file", + "fileName": "resources/files/addedFile.diff", + "isSuccessful": true + } + ] +} diff --git a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts index 7935372f429..4e1ce627bd3 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformationResultsHandler.test.ts @@ -4,13 +4,19 @@ */ import assert from 'assert' import sinon from 'sinon' -import fs from 'fs-extra' -import os from 'os' import { DiffModel, AddedChangeNode, ModifiedChangeNode } from 'aws-core-vscode/codewhisperer/node' +import { DescriptionContent } from 'aws-core-vscode/codewhisperer' import path from 'path' import { getTestResourceFilePath } from './amazonQGumbyUtil' +import { fs } from 'aws-core-vscode/shared' +import { createTestWorkspace } from 'aws-core-vscode/test' describe('DiffModel', function () { + let parsedTestDescriptions: DescriptionContent + beforeEach(async () => { + parsedTestDescriptions = JSON.parse(await fs.readFileText(getTestResourceFilePath('resources/files/diff.json'))) + }) + afterEach(() => { sinon.restore() }) @@ -20,7 +26,7 @@ describe('DiffModel', function () { const workspacePath = 'workspace' - sinon.replace(fs, 'existsSync', (path) => { + sinon.replace(fs, 'exists', async (path) => { const pathStr = path.toString() if (pathStr.includes(workspacePath)) { return false @@ -28,11 +34,19 @@ describe('DiffModel', function () { return true }) + testDiffModel.parseDiff( + getTestResourceFilePath('resources/files/addedFile.diff'), + workspacePath, + parsedTestDescriptions.content[0], + 1 + ) - testDiffModel.parseDiff(getTestResourceFilePath('resources/files/addedFile.diff'), workspacePath) - - assert.strictEqual(testDiffModel.changes.length, 1) - const change = testDiffModel.changes[0] + assert.strictEqual( + testDiffModel.patchFileNodes[0].patchFilePath, + getTestResourceFilePath('resources/files/addedFile.diff') + ) + assert(testDiffModel.patchFileNodes[0].label.includes(parsedTestDescriptions.content[0].name)) + const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof AddedChangeNode, true) }) @@ -40,22 +54,56 @@ describe('DiffModel', function () { it('WHEN parsing a diff patch where a file was modified THEN returns an array representing the modified file', async function () { const testDiffModel = new DiffModel() - const workspacePath = os.tmpdir() + const fileAmount = 1 + const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) - sinon.replace(fs, 'existsSync', (path) => true) - - fs.writeFileSync( - path.join(workspacePath, 'README.md'), + await fs.writeFile( + path.join(workspaceFolder.uri.fsPath, 'README.md'), 'This guide walks you through using Gradle to build a simple Java project.' ) - testDiffModel.parseDiff(getTestResourceFilePath('resources/files/modifiedFile.diff'), workspacePath) + testDiffModel.parseDiff( + getTestResourceFilePath('resources/files/modifiedFile.diff'), + workspaceFolder.uri.fsPath, + parsedTestDescriptions.content[0], + 1 + ) - assert.strictEqual(testDiffModel.changes.length, 1) - const change = testDiffModel.changes[0] + assert.strictEqual( + testDiffModel.patchFileNodes[0].patchFilePath, + getTestResourceFilePath('resources/files/modifiedFile.diff') + ) + assert(testDiffModel.patchFileNodes[0].label.includes(parsedTestDescriptions.content[0].name)) + const change = testDiffModel.patchFileNodes[0].children[0] assert.strictEqual(change instanceof ModifiedChangeNode, true) + }) + + it('WHEN parsing a diff patch where diff.json is not present and a file was modified THEN returns an array representing the modified file', async function () { + const testDiffModel = new DiffModel() + + const fileAmount = 1 + const workspaceFolder = await createTestWorkspace(fileAmount, { fileContent: '' }) + + await fs.writeFile( + path.join(workspaceFolder.uri.fsPath, 'README.md'), + 'This guide walks you through using Gradle to build a simple Java project.' + ) - fs.rmSync(path.join(workspacePath, 'README.md')) + testDiffModel.parseDiff( + getTestResourceFilePath('resources/files/modifiedFile.diff'), + workspaceFolder.uri.fsPath, + undefined, + 1 + ) + + assert.strictEqual( + testDiffModel.patchFileNodes[0].patchFilePath, + getTestResourceFilePath('resources/files/modifiedFile.diff') + ) + assert(testDiffModel.patchFileNodes[0].label.endsWith('modifiedFile.diff')) + const change = testDiffModel.patchFileNodes[0].children[0] + + assert.strictEqual(change instanceof ModifiedChangeNode, true) }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts index 6dfc46ee9dd..5af3252ec82 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onAcceptance.test.ts @@ -8,18 +8,14 @@ import * as vscode from 'vscode' import * as sinon from 'sinon' import { onAcceptance, - UserGroup, AcceptedSuggestionEntry, session, CodeWhispererTracker, RecommendationHandler, - CodeWhispererUserGroupSettings, AuthUtil, } from 'aws-core-vscode/codewhisperer' import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test' import { assertTelemetryCurried } from 'aws-core-vscode/test' -import { globals } from 'aws-core-vscode/shared' -import { extensionVersion } from 'aws-core-vscode/shared' describe('onAcceptance', function () { describe('onAcceptance', function () { @@ -31,7 +27,6 @@ describe('onAcceptance', function () { afterEach(function () { sinon.restore() session.reset() - CodeWhispererUserGroupSettings.instance.reset() }) it('Should enqueue an event object to tracker', async function () { @@ -75,11 +70,6 @@ describe('onAcceptance', function () { }) it('Should report telemetry that records this user decision event', async function () { - await globals.globalState.update('CODEWHISPERER_USER_GROUP', { - group: UserGroup.Control, - version: extensionVersion, - }) - const testStartUrl = 'testStartUrl' sinon.stub(AuthUtil.instance, 'startUrl').value(testStartUrl) const mockEditor = createMockTextEditor() @@ -117,7 +107,6 @@ describe('onAcceptance', function () { codewhispererCompletionType: 'Line', codewhispererLanguage: 'python', credentialStartUrl: testStartUrl, - codewhispererUserGroup: 'Control', }) }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts index 082114dc7ae..ae02e7bd7c3 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/onInlineAcceptance.test.ts @@ -8,14 +8,7 @@ import * as vscode from 'vscode' import * as sinon from 'sinon' import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test' import { assertTelemetryCurried } from 'aws-core-vscode/test' -import { - onInlineAcceptance, - RecommendationHandler, - AuthUtil, - session, - CodeWhispererUserGroupSettings, - UserGroup, -} from 'aws-core-vscode/codewhisperer' +import { onInlineAcceptance, RecommendationHandler, AuthUtil, session } from 'aws-core-vscode/codewhisperer' import { globals } from 'aws-core-vscode/shared' import { extensionVersion } from 'aws-core-vscode/shared' @@ -29,7 +22,6 @@ describe('onInlineAcceptance', function () { afterEach(function () { sinon.restore() session.reset() - CodeWhispererUserGroupSettings.instance.reset() }) it('Should dispose inline completion provider', async function () { @@ -53,7 +45,6 @@ describe('onInlineAcceptance', function () { it('Should report telemetry that records this user decision event', async function () { await globals.globalState.update('CODEWHISPERER_USER_GROUP', { - group: UserGroup.Classifier, version: extensionVersion, }) @@ -95,7 +86,6 @@ describe('onInlineAcceptance', function () { codewhispererCompletionType: 'Line', codewhispererLanguage: 'python', credentialStartUrl: testStartUrl, - codewhispererUserGroup: 'Classifier', }) }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/models/model.test.ts b/packages/amazonq/test/unit/codewhisperer/models/model.test.ts new file mode 100644 index 00000000000..ae7114a22c8 --- /dev/null +++ b/packages/amazonq/test/unit/codewhisperer/models/model.test.ts @@ -0,0 +1,73 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import assert from 'assert' +import sinon from 'sinon' +import { SecurityIssueFilters, SecurityTreeViewFilterState } from 'aws-core-vscode/codewhisperer' +import { globals } from 'aws-core-vscode/shared' + +describe('model', function () { + describe('SecurityTreeViewFilterState', function () { + let securityTreeViewFilterState: SecurityTreeViewFilterState + + beforeEach(function () { + securityTreeViewFilterState = SecurityTreeViewFilterState.instance + }) + + afterEach(function () { + sinon.restore() + }) + + it('should get the state', async function () { + const state: SecurityIssueFilters = { + severity: { + Critical: false, + High: true, + Medium: true, + Low: true, + Info: true, + }, + } + await globals.globalState.update('aws.amazonq.securityIssueFilters', state) + assert.deepStrictEqual(securityTreeViewFilterState.getState(), state) + }) + + it('should set the state', async function () { + await globals.globalState.update('aws.amazonq.securityIssueFilters', { + severity: { + Critical: true, + High: true, + Medium: true, + Low: true, + Info: true, + }, + } satisfies SecurityIssueFilters) + const state = { + severity: { + Critical: false, + High: true, + Medium: true, + Low: true, + Info: true, + }, + } satisfies SecurityIssueFilters + await securityTreeViewFilterState.setState(state) + assert.deepStrictEqual(globals.globalState.get('aws.amazonq.securityIssueFilters'), state) + }) + + it('should get hidden severities', async function () { + await globals.globalState.update('aws.amazonq.securityIssueFilters', { + severity: { + Critical: true, + High: false, + Medium: true, + Low: false, + Info: true, + }, + } satisfies SecurityIssueFilters) + const hiddenSeverities = securityTreeViewFilterState.getHiddenSeverities() + assert.deepStrictEqual(hiddenSeverities, ['High', 'Low']) + }) + }) +}) diff --git a/packages/amazonq/test/unit/codewhisperer/service/diagnosticsProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/diagnosticsProvider.test.ts index 4bf076eb23c..910aadb02b9 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/diagnosticsProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/diagnosticsProvider.test.ts @@ -12,6 +12,8 @@ import { removeDiagnostic, disposeSecurityDiagnostic, SecurityDiagnostic, + createSecurityDiagnostic, + codewhispererDiagnosticSourceLabel, } from 'aws-core-vscode/codewhisperer' import { createCodeScanIssue, createMockDocument, createTextDocumentChangeEvent } from 'aws-core-vscode/test' @@ -83,4 +85,16 @@ describe('diagnosticsProvider', function () { assert.strictEqual(actual[1].range.start.line, 5) assert.strictEqual(actual[1].range.end.line, 6) }) + + it('should create securityDiagnostic from codeScanIssue', function () { + const codeScanIssue = createCodeScanIssue() + const securityDiagnostic = createSecurityDiagnostic(codeScanIssue) + assert.strictEqual(securityDiagnostic.findingId, codeScanIssue.findingId) + assert.strictEqual(securityDiagnostic.message, codeScanIssue.title) + assert.strictEqual(securityDiagnostic.range.start.line, codeScanIssue.startLine) + assert.strictEqual(securityDiagnostic.range.end.line, codeScanIssue.endLine) + assert.strictEqual(securityDiagnostic.severity, vscode.DiagnosticSeverity.Warning) + assert.strictEqual(securityDiagnostic.source, codewhispererDiagnosticSourceLabel) + assert.strictEqual(securityDiagnostic.code, codeScanIssue.ruleId) + }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts b/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts index 0b1160f5117..075dc769b0e 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts @@ -13,14 +13,12 @@ import { resetCodeWhispererGlobalVariables, } from 'aws-core-vscode/test' import * as EditorContext from 'aws-core-vscode/codewhisperer' -import * as CodeWhispererConstants from 'aws-core-vscode/codewhisperer' import { ConfigurationEntry, DocumentChangedSource, KeyStrokeHandler, DefaultDocumentChangedType, RecommendationService, - CodeWhispererUserGroupSettings, ClassifierTrigger, isInlineCompletionEnabled, RecommendationHandler, @@ -52,7 +50,6 @@ describe('keyStrokeHandler', function () { }) afterEach(function () { sinon.restore() - CodeWhispererUserGroupSettings.instance.reset() }) it('Whatever the input is, should skip when automatic trigger is turned off, should not call invokeAutomatedTrigger', async function () { @@ -145,19 +142,13 @@ describe('keyStrokeHandler', function () { } }) - async function testShouldInvoke( - input: string, - shouldTrigger: boolean, - rightContext: string = '', - userGroup: CodeWhispererConstants.UserGroup = CodeWhispererConstants.UserGroup.Control - ) { + async function testShouldInvoke(input: string, shouldTrigger: boolean, rightContext: string = '') { const mockEditor = createMockTextEditor(rightContext, 'test.js', 'javascript', 0, 0) const mockEvent: vscode.TextDocumentChangeEvent = createTextDocumentChangeEvent( mockEditor.document, new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 1)), input ) - CodeWhispererUserGroupSettings.instance.userGroup = userGroup await KeyStrokeHandler.instance.processKeyStroke(mockEvent, mockEditor, mockClient, config) assert.strictEqual( invokeSpy.called, diff --git a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts b/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts index 4330ac823dc..4bc10329f81 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts @@ -10,13 +10,11 @@ import { ReferenceInlineProvider, session, AuthUtil, - CodeWhispererUserGroupSettings, DefaultCodeWhispererClient, RecommendationsList, ConfigurationEntry, RecommendationHandler, CodeWhispererCodeCoverageTracker, - UserGroup, supplementalContextUtil, } from 'aws-core-vscode/codewhisperer' import { @@ -26,7 +24,6 @@ import { resetCodeWhispererGlobalVariables, } from 'aws-core-vscode/test' // import * as supplementalContextUtil from 'aws-core-vscode/codewhisperer' -import { globals, extensionVersion } from 'aws-core-vscode/shared' describe('recommendationHandler', function () { const config: ConfigurationEntry = { @@ -55,7 +52,6 @@ describe('recommendationHandler', function () { afterEach(function () { sinon.restore() - CodeWhispererUserGroupSettings.instance.reset() }) it('should assign correct recommendations given input', async function () { @@ -110,11 +106,6 @@ describe('recommendationHandler', function () { }) it('should call telemetry function that records a CodeWhisperer service invocation', async function () { - await globals.globalState.update('CODEWHISPERER_USER_GROUP', { - group: UserGroup.CrossFile, - version: extensionVersion, - }) - const mockServerResult = { recommendations: [{ content: "print('Hello World!')" }, { content: '' }], $response: { @@ -157,16 +148,10 @@ describe('recommendationHandler', function () { codewhispererSupplementalContextTimeout: false, codewhispererSupplementalContextLatency: 0, codewhispererSupplementalContextLength: 100, - codewhispererUserGroup: 'CrossFile', }) }) it('should call telemetry function that records a Empty userDecision event', async function () { - await globals.globalState.update('CODEWHISPERER_USER_GROUP', { - group: UserGroup.CrossFile, - version: extensionVersion, - }) - const mockServerResult = { recommendations: [], nextToken: '', @@ -198,7 +183,6 @@ describe('recommendationHandler', function () { codewhispererCompletionType: 'Line', codewhispererLanguage: 'python', credentialStartUrl: testStartUrl, - codewhispererUserGroup: 'CrossFile', }) }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueCodeActionProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueCodeActionProvider.test.ts index 3015fef6ff1..3ac473cbcca 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityIssueCodeActionProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueCodeActionProvider.test.ts @@ -6,15 +6,17 @@ import * as vscode from 'vscode' import { createCodeActionContext, createCodeScanIssue, createMockDocument } from 'aws-core-vscode/test' import assert from 'assert' -import { SecurityIssueCodeActionProvider } from 'aws-core-vscode/codewhisperer' +import { SecurityIssueCodeActionProvider, SecurityIssueProvider } from 'aws-core-vscode/codewhisperer' describe('securityIssueCodeActionProvider', () => { + let securityIssueProvider: SecurityIssueProvider let securityIssueCodeActionProvider: SecurityIssueCodeActionProvider let mockDocument: vscode.TextDocument let context: vscode.CodeActionContext let token: vscode.CancellationTokenSource beforeEach(() => { + securityIssueProvider = SecurityIssueProvider.instance securityIssueCodeActionProvider = new SecurityIssueCodeActionProvider() mockDocument = createMockDocument('def two_sum(nums, target):\nfor', 'test.py', 'python') context = createCodeActionContext() @@ -22,7 +24,7 @@ describe('securityIssueCodeActionProvider', () => { }) it('should provide quick fix for each issue that has a suggested fix', () => { - securityIssueCodeActionProvider.issues = [ + securityIssueProvider.issues = [ { filePath: mockDocument.fileName, issues: [createCodeScanIssue({ title: 'issue 1' }), createCodeScanIssue({ title: 'issue 2' })], @@ -31,23 +33,27 @@ describe('securityIssueCodeActionProvider', () => { const range = new vscode.Range(0, 0, 0, 0) const actual = securityIssueCodeActionProvider.provideCodeActions(mockDocument, range, context, token.token) - assert.strictEqual(actual.length, 6) + assert.strictEqual(actual.length, 10) assert.strictEqual(actual[0].title, 'Amazon Q: Fix "issue 1"') assert.strictEqual(actual[0].kind, vscode.CodeActionKind.QuickFix) assert.strictEqual(actual[1].title, 'Amazon Q: View details for "issue 1"') assert.strictEqual(actual[1].kind, vscode.CodeActionKind.QuickFix) assert.strictEqual(actual[2].title, 'Amazon Q: Explain "issue 1"') assert.strictEqual(actual[2].kind, vscode.CodeActionKind.QuickFix) - assert.strictEqual(actual[3].title, 'Amazon Q: Fix "issue 2"') + assert.strictEqual(actual[3].title, 'Amazon Q: Ignore this "issue 1" issue') assert.strictEqual(actual[3].kind, vscode.CodeActionKind.QuickFix) - assert.strictEqual(actual[4].title, 'Amazon Q: View details for "issue 2"') + assert.strictEqual(actual[4].title, 'Amazon Q: Ignore all "issue 1" issues') assert.strictEqual(actual[4].kind, vscode.CodeActionKind.QuickFix) - assert.strictEqual(actual[5].title, 'Amazon Q: Explain "issue 2"') + assert.strictEqual(actual[5].title, 'Amazon Q: Fix "issue 2"') assert.strictEqual(actual[5].kind, vscode.CodeActionKind.QuickFix) + assert.strictEqual(actual[6].title, 'Amazon Q: View details for "issue 2"') + assert.strictEqual(actual[6].kind, vscode.CodeActionKind.QuickFix) + assert.strictEqual(actual[7].title, 'Amazon Q: Explain "issue 2"') + assert.strictEqual(actual[7].kind, vscode.CodeActionKind.QuickFix) }) it('should not provide quick fix if the issue does not have a suggested fix', () => { - securityIssueCodeActionProvider.issues = [ + securityIssueProvider.issues = [ { filePath: mockDocument.fileName, issues: [createCodeScanIssue({ title: 'issue 1', suggestedFixes: [] })], @@ -56,15 +62,19 @@ describe('securityIssueCodeActionProvider', () => { const range = new vscode.Range(0, 0, 0, 0) const actual = securityIssueCodeActionProvider.provideCodeActions(mockDocument, range, context, token.token) - assert.strictEqual(actual.length, 2) + assert.strictEqual(actual.length, 4) assert.strictEqual(actual[0].title, 'Amazon Q: View details for "issue 1"') assert.strictEqual(actual[0].kind, vscode.CodeActionKind.QuickFix) assert.strictEqual(actual[1].title, 'Amazon Q: Explain "issue 1"') assert.strictEqual(actual[1].kind, vscode.CodeActionKind.QuickFix) + assert.strictEqual(actual[2].title, 'Amazon Q: Ignore this "issue 1" issue') + assert.strictEqual(actual[2].kind, vscode.CodeActionKind.QuickFix) + assert.strictEqual(actual[3].title, 'Amazon Q: Ignore all "issue 1" issues') + assert.strictEqual(actual[3].kind, vscode.CodeActionKind.QuickFix) }) it('should skip issues not in the current file', () => { - securityIssueCodeActionProvider.issues = [ + securityIssueProvider.issues = [ { filePath: 'some/path', issues: [createCodeScanIssue({ title: 'issue 1' })], @@ -77,9 +87,24 @@ describe('securityIssueCodeActionProvider', () => { const range = new vscode.Range(0, 0, 0, 0) const actual = securityIssueCodeActionProvider.provideCodeActions(mockDocument, range, context, token.token) - assert.strictEqual(actual.length, 3) + assert.strictEqual(actual.length, 5) assert.strictEqual(actual[0].title, 'Amazon Q: Fix "issue 2"') assert.strictEqual(actual[1].title, 'Amazon Q: View details for "issue 2"') assert.strictEqual(actual[2].title, 'Amazon Q: Explain "issue 2"') + assert.strictEqual(actual[3].title, 'Amazon Q: Ignore this "issue 2" issue') + assert.strictEqual(actual[4].title, 'Amazon Q: Ignore all "issue 2" issues') + }) + + it('should not show issues that are not visible', () => { + securityIssueProvider.issues = [ + { + filePath: mockDocument.fileName, + issues: [createCodeScanIssue({ visible: false })], + }, + ] + const range = new vscode.Range(0, 0, 0, 0) + const actual = securityIssueCodeActionProvider.provideCodeActions(mockDocument, range, context, token.token) + + assert.strictEqual(actual.length, 0) }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts index 162f7534218..956c3b43d73 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts @@ -4,16 +4,18 @@ */ import * as vscode from 'vscode' -import { SecurityIssueHoverProvider } from 'aws-core-vscode/codewhisperer' +import { SecurityIssueHoverProvider, SecurityIssueProvider } from 'aws-core-vscode/codewhisperer' import { createCodeScanIssue, createMockDocument, assertTelemetry } from 'aws-core-vscode/test' import assert from 'assert' describe('securityIssueHoverProvider', () => { + let securityIssueProvider: SecurityIssueProvider let securityIssueHoverProvider: SecurityIssueHoverProvider let mockDocument: vscode.TextDocument let token: vscode.CancellationTokenSource beforeEach(() => { + securityIssueProvider = SecurityIssueProvider.instance securityIssueHoverProvider = new SecurityIssueHoverProvider() mockDocument = createMockDocument('def two_sum(nums, target):\nfor', 'test.py', 'python') token = new vscode.CancellationTokenSource() @@ -30,7 +32,7 @@ describe('securityIssueHoverProvider', () => { }), ] - securityIssueHoverProvider.issues = [ + securityIssueProvider.issues = [ { filePath: mockDocument.fileName, issues, @@ -46,10 +48,16 @@ describe('securityIssueHoverProvider', () => { 'fix\n\n' + `[$(eye) View Details](command:aws.amazonq.openSecurityIssuePanel?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Open "Amazon Q Security Issue"')\n` + + )} 'Open "Code Issue Details"')\n` + ` | [$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( JSON.stringify([issues[0]]) )} 'Explain with Amazon Q')\n` + + ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( + JSON.stringify([issues[0], mockDocument.fileName, 'hover']) + )} 'Ignore Issue')\n` + + ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( + JSON.stringify([issues[0], 'hover']) + )} 'Ignore Similar Issues')\n` + ` | [$(wrench) Fix](command:aws.amazonq.applySecurityFix?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName, 'hover']) )} 'Fix with Amazon Q')\n` + @@ -90,10 +98,16 @@ describe('securityIssueHoverProvider', () => { 'recommendationText\n\n' + `[$(eye) View Details](command:aws.amazonq.openSecurityIssuePanel?${encodeURIComponent( JSON.stringify([issues[1], mockDocument.fileName]) - )} 'Open "Amazon Q Security Issue"')\n` + + )} 'Open "Code Issue Details"')\n` + ` | [$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( JSON.stringify([issues[1]]) - )} 'Explain with Amazon Q')\n` + )} 'Explain with Amazon Q')\n` + + ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( + JSON.stringify([issues[1], mockDocument.fileName, 'hover']) + )} 'Ignore Issue')\n` + + ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( + JSON.stringify([issues[1], 'hover']) + )} 'Ignore Similar Issues')\n` ) assertTelemetry('codewhisperer_codeScanIssueHover', [ { findingId: 'finding-1', detectorId: 'language/detector-1', ruleId: 'Rule-123', includesFix: true }, @@ -102,7 +116,7 @@ describe('securityIssueHoverProvider', () => { }) it('should return empty contents if there is no issue on the current position', () => { - securityIssueHoverProvider.issues = [ + securityIssueProvider.issues = [ { filePath: mockDocument.fileName, issues: [createCodeScanIssue()], @@ -114,7 +128,7 @@ describe('securityIssueHoverProvider', () => { }) it('should skip issues not in the current file', () => { - securityIssueHoverProvider.issues = [ + securityIssueProvider.issues = [ { filePath: 'some/path', issues: [createCodeScanIssue()], @@ -130,7 +144,7 @@ describe('securityIssueHoverProvider', () => { it('should not show severity badge if undefined', () => { const issues = [createCodeScanIssue({ severity: undefined, suggestedFixes: [] })] - securityIssueHoverProvider.issues = [ + securityIssueProvider.issues = [ { filePath: mockDocument.fileName, issues, @@ -144,10 +158,16 @@ describe('securityIssueHoverProvider', () => { 'recommendationText\n\n' + `[$(eye) View Details](command:aws.amazonq.openSecurityIssuePanel?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Open "Amazon Q Security Issue"')\n` + + )} 'Open "Code Issue Details"')\n` + ` | [$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( JSON.stringify([issues[0]]) - )} 'Explain with Amazon Q')\n` + )} 'Explain with Amazon Q')\n` + + ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( + JSON.stringify([issues[0], mockDocument.fileName, 'hover']) + )} 'Ignore Issue')\n` + + ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( + JSON.stringify([issues[0], 'hover']) + )} 'Ignore Similar Issues')\n` ) }) @@ -162,7 +182,7 @@ describe('securityIssueHoverProvider', () => { ], }), ] - securityIssueHoverProvider.issues = [ + securityIssueProvider.issues = [ { filePath: mockDocument.fileName, issues, @@ -176,10 +196,16 @@ describe('securityIssueHoverProvider', () => { 'fix\n\n' + `[$(eye) View Details](command:aws.amazonq.openSecurityIssuePanel?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Open "Amazon Q Security Issue"')\n` + + )} 'Open "Code Issue Details"')\n` + ` | [$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( JSON.stringify([issues[0]]) )} 'Explain with Amazon Q')\n` + + ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( + JSON.stringify([issues[0], mockDocument.fileName, 'hover']) + )} 'Ignore Issue')\n` + + ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( + JSON.stringify([issues[0], 'hover']) + )} 'Ignore Similar Issues')\n` + ` | [$(wrench) Fix](command:aws.amazonq.applySecurityFix?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName, 'hover']) )} 'Fix with Amazon Q')\n` + @@ -216,4 +242,16 @@ describe('securityIssueHoverProvider', () => { '\n\n' ) }) + + it('should not show issues that are not visible', () => { + const issues = [createCodeScanIssue({ visible: false })] + securityIssueProvider.issues = [ + { + filePath: mockDocument.fileName, + issues, + }, + ] + const actual = securityIssueHoverProvider.provideHover(mockDocument, new vscode.Position(0, 0), token.token) + assert.strictEqual(actual.contents.length, 0) + }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueProvider.test.ts index 35af4440db1..cbe4daed9fb 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityIssueProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueProvider.test.ts @@ -37,14 +37,14 @@ describe('securityIssueProvider', () => { ] assert.strictEqual(mockProvider.issues[0].issues[0].startLine, 1) assert.strictEqual(mockProvider.issues[0].issues[0].endLine, 2) - assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code.startsWith('@@ -1,1 +1,1 @@')) + assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code?.startsWith('@@ -1,1 +1,1 @@')) const changeEvent = createTextDocumentChangeEvent(mockDocument, new vscode.Range(0, 0, 0, 0), '\n') mockProvider.handleDocumentChange(changeEvent) assert.strictEqual(mockProvider.issues[0].issues[0].startLine, 2) assert.strictEqual(mockProvider.issues[0].issues[0].endLine, 3) - assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code.startsWith('@@ -2,1 +2,1 @@')) + assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code?.startsWith('@@ -2,1 +2,1 @@')) }) it('does not move the issue if the document changed below the line', () => { @@ -52,14 +52,14 @@ describe('securityIssueProvider', () => { assert.strictEqual(mockProvider.issues[0].issues[0].startLine, 0) assert.strictEqual(mockProvider.issues[0].issues[0].endLine, 1) - assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code.startsWith('@@ -1,1 +1,1 @@')) + assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code?.startsWith('@@ -1,1 +1,1 @@')) const changeEvent = createTextDocumentChangeEvent(mockDocument, new vscode.Range(2, 0, 2, 0), '\n') mockProvider.handleDocumentChange(changeEvent) assert.strictEqual(mockProvider.issues[0].issues[0].startLine, 0) assert.strictEqual(mockProvider.issues[0].issues[0].endLine, 1) - assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code.startsWith('@@ -1,1 +1,1 @@')) + assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code?.startsWith('@@ -1,1 +1,1 @@')) }) it('should do nothing if no content changes', () => { @@ -69,7 +69,7 @@ describe('securityIssueProvider', () => { assert.strictEqual(mockProvider.issues[0].issues[0].startLine, 1) assert.strictEqual(mockProvider.issues[0].issues[0].endLine, 2) - assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code.startsWith('@@ -1,1 +1,1 @@')) + assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code?.startsWith('@@ -1,1 +1,1 @@')) const changeEvent = createTextDocumentChangeEvent(mockDocument, new vscode.Range(0, 0, 0, 0), '') changeEvent.contentChanges = [] @@ -77,21 +77,21 @@ describe('securityIssueProvider', () => { assert.strictEqual(mockProvider.issues[0].issues[0].startLine, 1) assert.strictEqual(mockProvider.issues[0].issues[0].endLine, 2) - assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code.startsWith('@@ -1,1 +1,1 @@')) + assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code?.startsWith('@@ -1,1 +1,1 @@')) }) it('should do nothing if file path does not match', () => { mockProvider.issues = [{ filePath: 'some/path', issues: [createCodeScanIssue({ startLine: 1, endLine: 2 })] }] assert.strictEqual(mockProvider.issues[0].issues[0].startLine, 1) assert.strictEqual(mockProvider.issues[0].issues[0].endLine, 2) - assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code.startsWith('@@ -1,1 +1,1 @@')) + assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code?.startsWith('@@ -1,1 +1,1 @@')) const changeEvent = createTextDocumentChangeEvent(mockDocument, new vscode.Range(0, 0, 0, 0), '\n') mockProvider.handleDocumentChange(changeEvent) assert.strictEqual(mockProvider.issues[0].issues[0].startLine, 1) assert.strictEqual(mockProvider.issues[0].issues[0].endLine, 2) - assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code.startsWith('@@ -1,1 +1,1 @@')) + assert.ok(mockProvider.issues[0].issues[0].suggestedFixes[0].code?.startsWith('@@ -1,1 +1,1 @@')) }) describe('removeIssue', () => { diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts new file mode 100644 index 00000000000..bd7c3aab8de --- /dev/null +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts @@ -0,0 +1,106 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + FileItem, + IssueItem, + SecurityIssueTreeViewProvider, + SecurityTreeViewFilterState, + SecurityIssueProvider, + SeverityItem, +} from 'aws-core-vscode/codewhisperer' +import { createCodeScanIssue } from 'aws-core-vscode/test' +import assert from 'assert' +import sinon from 'sinon' + +describe('SecurityIssueTreeViewProvider', function () { + let securityIssueProvider: SecurityIssueProvider + let securityIssueTreeViewProvider: SecurityIssueTreeViewProvider + + beforeEach(function () { + securityIssueProvider = SecurityIssueProvider.instance + securityIssueTreeViewProvider = new SecurityIssueTreeViewProvider() + }) + + afterEach(function () { + sinon.restore() + }) + + describe('getTreeItem', function () { + it('should return the element as a FileItem', function () { + const element = new FileItem('dummy-path', []) + const result = securityIssueTreeViewProvider.getTreeItem(element) + assert.strictEqual(result, element) + }) + + it('should return the element as a IssueItem', function () { + const element = new IssueItem('dummy-path', createCodeScanIssue()) + const result = securityIssueTreeViewProvider.getTreeItem(element) + assert.strictEqual(result, element) + }) + }) + + describe('getChildren', function () { + it('should return sorted list of severities if element is undefined', function () { + securityIssueProvider.issues = [ + { filePath: 'file/path/c', issues: [createCodeScanIssue(), createCodeScanIssue()] }, + { filePath: 'file/path/d', issues: [createCodeScanIssue(), createCodeScanIssue()] }, + { filePath: 'file/path/a', issues: [createCodeScanIssue(), createCodeScanIssue()] }, + { filePath: 'file/path/b', issues: [createCodeScanIssue(), createCodeScanIssue()] }, + ] + + const element = undefined + const result = securityIssueTreeViewProvider.getChildren(element) as SeverityItem[] + assert.strictEqual(result.length, 5) + assert.strictEqual(result[0].label, 'Critical') + assert.strictEqual(result[0].description, '0 issues') + assert.strictEqual(result[1].label, 'High') + assert.strictEqual(result[1].description, '8 issues') + assert.strictEqual(result[2].label, 'Medium') + assert.strictEqual(result[2].description, '0 issues') + assert.strictEqual(result[3].label, 'Low') + assert.strictEqual(result[3].description, '0 issues') + assert.strictEqual(result[4].label, 'Info') + assert.strictEqual(result[4].description, '0 issues') + }) + + it('should return sorted list of issues if element is SeverityItem', function () { + const element = new SeverityItem('Critical', [ + { + ...createCodeScanIssue({ title: 'Finding A', startLine: 10, severity: 'Critical' }), + filePath: 'file/path/a', + }, + { + ...createCodeScanIssue({ title: 'Finding B', startLine: 2, severity: 'Critical' }), + filePath: 'file/path/b', + }, + ]) + const result = securityIssueTreeViewProvider.getChildren(element) as IssueItem[] + assert.strictEqual(result.length, 2) + assert.strictEqual(result[0].label, 'Finding A') + assert.strictEqual(result[1].label, 'Finding B') + }) + + it('should filter out severities', function () { + const element = undefined + let result = securityIssueTreeViewProvider.getChildren(element) as SeverityItem[] + assert.strictEqual(result.length, 5) + + sinon.stub(SecurityTreeViewFilterState.instance, 'getHiddenSeverities').returns(['Medium']) + + result = securityIssueTreeViewProvider.getChildren(element) as SeverityItem[] + assert.strictEqual(result.length, 4) + assert.ok(result.every((item) => item.severity !== 'Medium')) + }) + + it('should not show issues that are not visible', function () { + const element = new SeverityItem('Critical', [ + { ...createCodeScanIssue({ visible: false }), filePath: 'file/path/a' }, + ]) + const result = securityIssueTreeViewProvider.getChildren(element) as IssueItem[] + assert.strictEqual(result.length, 0) + }) + }) +}) diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityScanHandler.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityScanHandler.test.ts index c88a9001a87..b0086b2a205 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityScanHandler.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityScanHandler.test.ts @@ -17,35 +17,37 @@ import { import assert from 'assert' import sinon from 'sinon' import * as vscode from 'vscode' -import fs from 'fs' - -const mockCodeScanFindings = JSON.stringify([ - { - filePath: 'workspaceFolder/python3.7-plain-sam-app/hello_world/app.py', - startLine: 1, - endLine: 1, - title: 'title', - description: { +import fs from 'fs' // eslint-disable-line no-restricted-imports + +const buildRawCodeScanIssue = (params?: Partial): RawCodeScanIssue => ({ + filePath: 'workspaceFolder/python3.7-plain-sam-app/hello_world/app.py', + startLine: 1, + endLine: 1, + title: 'title', + description: { + text: 'text', + markdown: 'markdown', + }, + detectorId: 'detectorId', + detectorName: 'detectorName', + findingId: 'findingId', + relatedVulnerabilities: [], + severity: 'High', + remediation: { + recommendation: { text: 'text', - markdown: 'markdown', - }, - detectorId: 'detectorId', - detectorName: 'detectorName', - findingId: 'findingId', - relatedVulnerabilities: [], - severity: 'High', - remediation: { - recommendation: { - text: 'text', - url: 'url', - }, - suggestedFixes: [], + url: 'url', }, - codeSnippet: [], - } satisfies RawCodeScanIssue, -]) + suggestedFixes: [], + }, + codeSnippet: [], + ...params, +}) -const mockListCodeScanFindingsResponse: Awaited>> = { +const buildMockListCodeScanFindingsResponse = ( + codeScanFindings: string = JSON.stringify([buildRawCodeScanIssue()]), + nextToken?: boolean +): Awaited>> => ({ $response: { hasNextPage: () => false, nextPage: () => undefined, @@ -56,16 +58,9 @@ const mockListCodeScanFindingsResponse: Awaited> -> = { - ...mockListCodeScanFindingsResponse, - nextToken: 'nextToken', -} + codeScanFindings, + nextToken: nextToken ? 'nextToken' : undefined, +}) describe('securityScanHandler', function () { describe('listScanResults', function () { @@ -81,7 +76,7 @@ describe('securityScanHandler', function () { }) it('should make ListCodeScanFindings request and aggregate findings by file path', async function () { - mockClient.listCodeScanFindings.resolves(mockListCodeScanFindingsResponse) + mockClient.listCodeScanFindings.resolves(buildMockListCodeScanFindingsResponse()) const aggregatedCodeScanIssueList = await listScanResults( mockClient, @@ -100,11 +95,26 @@ describe('securityScanHandler', function () { it('should handle ListCodeScanFindings request with paginated response', async function () { mockClient.listCodeScanFindings .onFirstCall() - .resolves(mockListCodeScanFindingsPaginatedResponse) + .resolves( + buildMockListCodeScanFindingsResponse( + JSON.stringify([buildRawCodeScanIssue({ title: 'title1' })]), + true + ) + ) .onSecondCall() - .resolves(mockListCodeScanFindingsPaginatedResponse) + .resolves( + buildMockListCodeScanFindingsResponse( + JSON.stringify([buildRawCodeScanIssue({ title: 'title2' })]), + true + ) + ) .onThirdCall() - .resolves(mockListCodeScanFindingsResponse) + .resolves( + buildMockListCodeScanFindingsResponse( + JSON.stringify([buildRawCodeScanIssue({ title: 'title3' })]), + false + ) + ) const aggregatedCodeScanIssueList = await listScanResults( mockClient, @@ -154,7 +164,7 @@ describe('securityScanHandler', function () { { filePath: 'file2.ts', startLine: 1, endLine: 1, codeSnippet: [{ number: 1, content: 'line 1' }] }, ]) - mapToAggregatedList(codeScanIssueMap, json, editor, CodeAnalysisScope.FILE) + mapToAggregatedList(codeScanIssueMap, json, editor, CodeAnalysisScope.FILE_AUTO) assert.equal(codeScanIssueMap.size, 2) assert.equal(codeScanIssueMap.get('file1.ts')?.length, 1) @@ -175,7 +185,7 @@ describe('securityScanHandler', function () { { filePath: 'file1.ts', startLine: 3, endLine: 3, codeSnippet: [{ number: 3, content: 'line 3' }] }, ]) - mapToAggregatedList(codeScanIssueMap, json, editor, CodeAnalysisScope.FILE) + mapToAggregatedList(codeScanIssueMap, json, editor, CodeAnalysisScope.FILE_AUTO) assert.equal(codeScanIssueMap.size, 1) assert.equal(codeScanIssueMap.get('file1.ts')?.length, 2) @@ -195,7 +205,36 @@ describe('securityScanHandler', function () { { filePath: 'file1.ts', startLine: 3, endLine: 3, codeSnippet: [{ number: 3, content: '**** **' }] }, ]) - mapToAggregatedList(codeScanIssueMap, json, editor, CodeAnalysisScope.FILE) + mapToAggregatedList(codeScanIssueMap, json, editor, CodeAnalysisScope.FILE_AUTO) + assert.strictEqual(codeScanIssueMap.size, 1) + assert.strictEqual(codeScanIssueMap.get('file1.ts')?.length, 1) + }) + + it('should handle duplicate issues', function () { + const json = JSON.stringify([ + { + filePath: 'file1.ts', + startLine: 1, + endLine: 2, + title: 'duplicate issue', + codeSnippet: [ + { number: 1, content: 'line 1' }, + { number: 2, content: 'line 2' }, + ], + }, + { + filePath: 'file1.ts', + startLine: 1, + endLine: 2, + title: 'duplicate issue', + codeSnippet: [ + { number: 1, content: 'line 1' }, + { number: 2, content: 'line 2' }, + ], + }, + ]) + + mapToAggregatedList(codeScanIssueMap, json, editor, CodeAnalysisScope.FILE_AUTO) assert.strictEqual(codeScanIssueMap.size, 1) assert.strictEqual(codeScanIssueMap.get('file1.ts')?.length, 1) }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts b/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts index 750364ce061..0f1429f130b 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/telemetry.test.ts @@ -53,7 +53,6 @@ describe.skip('CodeWhisperer telemetry', async function () { codewhispererSuggestionState: 'Accept', codewhispererSuggestionImportCount: 0, codewhispererTypeaheadLength: 0, - codewhispererUserGroup: 'Control', ...ops, } } diff --git a/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererCodeCoverageTracker.test.ts b/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererCodeCoverageTracker.test.ts index 966dec30aab..ee001b3328d 100644 --- a/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererCodeCoverageTracker.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererCodeCoverageTracker.test.ts @@ -11,11 +11,10 @@ import { vsCodeState, TelemetryHelper, AuthUtil, - UserGroup, - CodeWhispererUserGroupSettings, + getUnmodifiedAcceptedTokens, } from 'aws-core-vscode/codewhisperer' import { createMockDocument, createMockTextEditor, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' -import { globals, extensionVersion } from 'aws-core-vscode/shared' +import { globals } from 'aws-core-vscode/shared' import { assertTelemetryCurried } from 'aws-core-vscode/test' describe('codewhispererCodecoverageTracker', function () { @@ -30,11 +29,6 @@ describe('codewhispererCodecoverageTracker', function () { it('unsupported language', function () { assert.strictEqual(CodeWhispererCodeCoverageTracker.getTracker('vb'), undefined) assert.strictEqual(CodeWhispererCodeCoverageTracker.getTracker('ipynb'), undefined) - assert.strictEqual(CodeWhispererCodeCoverageTracker.getTracker('r'), undefined) - assert.strictEqual(CodeWhispererCodeCoverageTracker.getTracker('dart'), undefined) - assert.strictEqual(CodeWhispererCodeCoverageTracker.getTracker('swift'), undefined) - assert.strictEqual(CodeWhispererCodeCoverageTracker.getTracker('lua'), undefined) - assert.strictEqual(CodeWhispererCodeCoverageTracker.getTracker('powershell'), undefined) }) it('supported language', function () { @@ -162,14 +156,13 @@ describe('codewhispererCodecoverageTracker', function () { }) it('Should return correct unmodified accepted tokens count', function () { - const tracker = CodeWhispererCodeCoverageTracker.getTracker(language) - assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('foo', 'fou'), 2) - assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('foo', 'f11111oo'), 3) - assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('foo', 'fo'), 2) - assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('helloworld', 'HelloWorld'), 8) - assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('helloworld', 'World'), 4) - assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('CodeWhisperer', 'CODE'), 1) - assert.strictEqual(tracker?.getUnmodifiedAcceptedTokens('CodeWhisperer', 'CodeWhispererGood'), 13) + assert.strictEqual(getUnmodifiedAcceptedTokens('foo', 'fou'), 2) + assert.strictEqual(getUnmodifiedAcceptedTokens('foo', 'f11111oo'), 3) + assert.strictEqual(getUnmodifiedAcceptedTokens('foo', 'fo'), 2) + assert.strictEqual(getUnmodifiedAcceptedTokens('helloworld', 'HelloWorld'), 8) + assert.strictEqual(getUnmodifiedAcceptedTokens('helloworld', 'World'), 4) + assert.strictEqual(getUnmodifiedAcceptedTokens('CodeWhisperer', 'CODE'), 1) + assert.strictEqual(getUnmodifiedAcceptedTokens('CodeWhisperer', 'CodeWhispererGood'), 13) }) }) @@ -516,21 +509,14 @@ describe('codewhispererCodecoverageTracker', function () { if (tracker) { sinon.stub(tracker, 'isActive').returns(true) } - CodeWhispererUserGroupSettings.instance.reset() }) afterEach(function () { sinon.restore() CodeWhispererCodeCoverageTracker.instances.clear() - CodeWhispererUserGroupSettings.instance.reset() }) it('should emit correct code coverage telemetry in python file', async function () { - await globals.globalState.update('CODEWHISPERER_USER_GROUP', { - group: UserGroup.Control, - version: extensionVersion, - }) - const tracker = CodeWhispererCodeCoverageTracker.getTracker(language) const assertTelemetry = assertTelemetryCurried('codewhisperer_codePercentage') @@ -545,16 +531,10 @@ describe('codewhispererCodecoverageTracker', function () { codewhispererSuggestedTokens: 7, codewhispererPercentage: 7, successCount: 1, - codewhispererUserGroup: 'Control', }) }) it('should emit correct code coverage telemetry when success count = 0', async function () { - await globals.globalState.update('CODEWHISPERER_USER_GROUP', { - group: UserGroup.Control, - version: extensionVersion, - }) - const tracker = CodeWhispererCodeCoverageTracker.getTracker('java') const assertTelemetry = assertTelemetryCurried('codewhisperer_codePercentage') @@ -574,7 +554,6 @@ describe('codewhispererCodecoverageTracker', function () { codewhispererSuggestedTokens: 18, codewhispererPercentage: 60, successCount: 2, - codewhispererUserGroup: 'Control', }) }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererTracker.test.ts b/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererTracker.test.ts index 8fc7e76257c..ed17c181ee5 100644 --- a/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererTracker.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererTracker.test.ts @@ -6,14 +6,9 @@ import assert from 'assert' import * as sinon from 'sinon' import { assertTelemetryCurried } from 'aws-core-vscode/test' -import { - AuthUtil, - CodeWhispererTracker, - UserGroup, - CodeWhispererUserGroupSettings, -} from 'aws-core-vscode/codewhisperer' +import { AuthUtil, CodeWhispererTracker } from 'aws-core-vscode/codewhisperer' import { resetCodeWhispererGlobalVariables, createAcceptedSuggestionEntry } from 'aws-core-vscode/test' -import { globals, extensionVersion } from 'aws-core-vscode/shared' +import { globals } from 'aws-core-vscode/shared' describe('codewhispererTracker', function () { describe('enqueue', function () { @@ -86,20 +81,7 @@ describe('codewhispererTracker', function () { }) describe('emitTelemetryOnSuggestion', function () { - beforeEach(function () { - CodeWhispererUserGroupSettings.instance.reset() - }) - - afterEach(function () { - CodeWhispererUserGroupSettings.instance.reset() - }) - it('Should call recordCodewhispererUserModification with suggestion event', async function () { - await globals.globalState.update('CODEWHISPERER_USER_GROUP', { - group: UserGroup.CrossFile, - version: extensionVersion, - }) - const testStartUrl = 'testStartUrl' sinon.stub(AuthUtil.instance, 'startUrl').value(testStartUrl) const suggestion = createAcceptedSuggestionEntry() @@ -114,7 +96,6 @@ describe('codewhispererTracker', function () { codewhispererCompletionType: 'Line', codewhispererLanguage: 'java', credentialStartUrl: testStartUrl, - codewhispererUserGroup: 'CrossFile', codewhispererCharactersAccepted: suggestion.originalString.length, codewhispererCharactersModified: 0, }) diff --git a/packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts b/packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts index f2f71c278d9..74a2c97dd75 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts @@ -13,12 +13,12 @@ import { } from 'aws-core-vscode/codewhisperer' import { assertTelemetry, - captureEventOnce, getTestWindow, SeverityLevel, createBuilderIdProfile, createSsoProfile, createTestAuth, + captureEventNTimes, } from 'aws-core-vscode/test' import { Auth, Connection, isAnySsoConnection, isBuilderIdConnection } from 'aws-core-vscode/auth' import { globals, vscodeComponent } from 'aws-core-vscode/shared' @@ -231,15 +231,13 @@ describe('AuthUtil', async function () { assert.strictEqual(auth.activeConnection?.id, authUtil.conn?.id) // Switch to unsupported connection - const cwAuthUpdatedConnection = captureEventOnce(authUtil.secondaryAuth.onDidChangeActiveConnection) + const cwAuthUpdatedConnection = captureEventNTimes(authUtil.secondaryAuth.onDidChangeActiveConnection, 2) await auth.useConnection(unsupportedConn) - // This is triggered when the main Auth connection is switched + // - This is triggered when the main Auth connection is switched + // - This is triggered by registerAuthListener() when it saves the previous active connection as a fallback. await cwAuthUpdatedConnection - // This is triggered by registerAuthListener() when it saves the previous active connection as a fallback. - // TODO in a refactor see if we can simplify multiple multiple triggers on the same event. - await captureEventOnce(authUtil.secondaryAuth.onDidChangeActiveConnection) - // Is using the fallback connection + // TODO in a refactor see if we can simplify multiple multiple triggers on the same event. assert.ok(authUtil.isConnected()) assert.ok(authUtil.isUsingSavedConnection) assert.notStrictEqual(auth.activeConnection?.id, authUtil.conn?.id) diff --git a/packages/amazonq/test/unit/codewhisperer/util/commonUtil.test.ts b/packages/amazonq/test/unit/codewhisperer/util/commonUtil.test.ts index eaf1ae332ba..5694b33365d 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/commonUtil.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/commonUtil.test.ts @@ -4,7 +4,11 @@ */ import assert from 'assert' -import { checkLeftContextKeywordsForJsonAndYaml, getPrefixSuffixOverlap } from 'aws-core-vscode/codewhisperer' +import { + JsonConfigFileNamingConvention, + checkLeftContextKeywordsForJson, + getPrefixSuffixOverlap, +} from 'aws-core-vscode/codewhisperer' describe('commonUtil', function () { describe('getPrefixSuffixOverlap', function () { @@ -31,29 +35,47 @@ describe('commonUtil', function () { }) }) - describe('checkLeftContextKeywordsForJsonAndYaml', function () { + describe('checkLeftContextKeywordsForJson', function () { it('Should return true for valid left context keywords', async function () { assert.strictEqual( - checkLeftContextKeywordsForJsonAndYaml('Create an S3 Bucket named CodeWhisperer', 'json'), - true - ) - assert.strictEqual( - checkLeftContextKeywordsForJsonAndYaml('Create an S3 Bucket named CodeWhisperer', 'yaml'), + checkLeftContextKeywordsForJson('foo.json', 'Create an S3 Bucket named CodeWhisperer', 'json'), true ) }) it('Should return false for invalid left context keywords', async function () { assert.strictEqual( - checkLeftContextKeywordsForJsonAndYaml('Create an S3 Bucket named CodeWhisperer in cfn', 'yaml'), - false - ) - assert.strictEqual( - checkLeftContextKeywordsForJsonAndYaml( + checkLeftContextKeywordsForJson( + 'foo.json', 'Create an S3 Bucket named CodeWhisperer in Cloudformation', 'json' ), false ) }) + + for (const jsonConfigFile of JsonConfigFileNamingConvention) { + it(`should evalute by filename ${jsonConfigFile}`, function () { + assert.strictEqual(checkLeftContextKeywordsForJson(jsonConfigFile, 'foo', 'json'), false) + + assert.strictEqual(checkLeftContextKeywordsForJson(jsonConfigFile.toUpperCase(), 'bar', 'json'), false) + + assert.strictEqual(checkLeftContextKeywordsForJson(jsonConfigFile.toUpperCase(), 'baz', 'json'), false) + }) + + const upperCaseFilename = jsonConfigFile.toUpperCase() + it(`should evalute by filename and case insensitive ${upperCaseFilename}`, function () { + assert.strictEqual(checkLeftContextKeywordsForJson(upperCaseFilename, 'foo', 'json'), false) + + assert.strictEqual( + checkLeftContextKeywordsForJson(upperCaseFilename.toUpperCase(), 'bar', 'json'), + false + ) + + assert.strictEqual( + checkLeftContextKeywordsForJson(upperCaseFilename.toUpperCase(), 'baz', 'json'), + false + ) + }) + } }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts b/packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts index 0395d09483e..75825232d7c 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/crossFileContextUtil.test.ts @@ -4,11 +4,12 @@ */ import assert from 'assert' +import * as FakeTimers from '@sinonjs/fake-timers' import * as vscode from 'vscode' import * as sinon from 'sinon' import * as crossFile from 'aws-core-vscode/codewhisperer' -import { createMockTextEditor } from 'aws-core-vscode/test' -import { CodeWhispererUserGroupSettings, UserGroup, crossFileContextConfig } from 'aws-core-vscode/codewhisperer' +import { aStringWithLineCount, createMockTextEditor, installFakeClock } from 'aws-core-vscode/test' +import { FeatureConfigProvider, crossFileContextConfig } from 'aws-core-vscode/codewhisperer' import { assertTabCount, closeAllEditors, @@ -19,8 +20,8 @@ import { } from 'aws-core-vscode/test' import { areEqual, normalize } from 'aws-core-vscode/shared' import * as path from 'path' +import { LspController } from 'aws-core-vscode/amazonq' -const userGroupSettings = CodeWhispererUserGroupSettings.instance let tempFolder: string describe('crossFileContextUtil', function () { @@ -30,35 +31,153 @@ describe('crossFileContextUtil', function () { } let mockEditor: vscode.TextEditor + let clock: FakeTimers.InstalledClock + + before(function () { + clock = installFakeClock() + }) + + after(function () { + clock.uninstall() + }) + + afterEach(function () { + sinon.restore() + }) describe('fetchSupplementalContextForSrc', function () { beforeEach(async function () { tempFolder = (await createTestWorkspaceFolder()).uri.fsPath }) - describe('should fetch 3 chunks and each chunk should contains 10 lines', function () { - async function assertCorrectCodeChunk() { - await toTextEditor(sampleFileOf60Lines, 'CrossFile.java', tempFolder, { preview: false }) - const myCurrentEditor = await toTextEditor('', 'TargetFile.java', tempFolder, { - preview: false, - }) - const actual = await crossFile.fetchSupplementalContextForSrc(myCurrentEditor, fakeCancellationToken) - assert.ok(actual) - assert.ok(actual.supplementalContextItems.length === 3) - - assert.strictEqual(actual.supplementalContextItems[0].content.split('\n').length, 10) - assert.strictEqual(actual.supplementalContextItems[1].content.split('\n').length, 10) - assert.strictEqual(actual.supplementalContextItems[2].content.split('\n').length, 10) - } + afterEach(async function () { + sinon.restore() + }) + + it.skip('for control group, should return opentabs context where there will be 3 chunks and each chunk should contains 50 lines', async function () { + sinon.stub(FeatureConfigProvider.instance, 'getProjectContextGroup').returns('control') + await toTextEditor(aStringWithLineCount(200), 'CrossFile.java', tempFolder, { preview: false }) + const myCurrentEditor = await toTextEditor('', 'TargetFile.java', tempFolder, { + preview: false, + }) + + await assertTabCount(2) - it('control group', async function () { - CodeWhispererUserGroupSettings.instance.userGroup = UserGroup.Control - await assertCorrectCodeChunk() + const actual = await crossFile.fetchSupplementalContextForSrc(myCurrentEditor, fakeCancellationToken) + assert.ok(actual) + assert.strictEqual(actual.supplementalContextItems.length, 3) + assert.strictEqual(actual.supplementalContextItems[0].content.split('\n').length, 50) + assert.strictEqual(actual.supplementalContextItems[1].content.split('\n').length, 50) + assert.strictEqual(actual.supplementalContextItems[2].content.split('\n').length, 50) + }) + + it('for t1 group, should return repomap + opentabs context', async function () { + await toTextEditor(aStringWithLineCount(200), 'CrossFile.java', tempFolder, { preview: false }) + const myCurrentEditor = await toTextEditor('', 'TargetFile.java', tempFolder, { + preview: false, }) - it('treatment group', async function () { - CodeWhispererUserGroupSettings.instance.userGroup = UserGroup.CrossFile - await assertCorrectCodeChunk() + await assertTabCount(2) + + sinon.stub(FeatureConfigProvider.instance, 'getProjectContextGroup').returns('t1') + sinon + .stub(LspController.instance, 'queryInlineProjectContext') + .withArgs(sinon.match.any, sinon.match.any, 'codemap') + .resolves([ + { + content: 'foo', + score: 0, + filePath: 'q-inline', + }, + ]) + + const actual = await crossFile.fetchSupplementalContextForSrc(myCurrentEditor, fakeCancellationToken) + assert.ok(actual) + assert.strictEqual(actual.supplementalContextItems.length, 4) + assert.strictEqual(actual?.strategy, 'codemap') + assert.deepEqual(actual?.supplementalContextItems[0], { + content: 'foo', + score: 0, + filePath: 'q-inline', + }) + + assert.strictEqual(actual.supplementalContextItems[1].content.split('\n').length, 50) + assert.strictEqual(actual.supplementalContextItems[2].content.split('\n').length, 50) + assert.strictEqual(actual.supplementalContextItems[3].content.split('\n').length, 50) + }) + + it.skip('for t2 group, should return global bm25 context and no repomap', async function () { + await toTextEditor(aStringWithLineCount(200), 'CrossFile.java', tempFolder, { preview: false }) + const myCurrentEditor = await toTextEditor('', 'TargetFile.java', tempFolder, { + preview: false, + }) + + await assertTabCount(2) + + sinon.stub(FeatureConfigProvider.instance, 'getProjectContextGroup').returns('t2') + sinon + .stub(LspController.instance, 'queryInlineProjectContext') + .withArgs(sinon.match.any, sinon.match.any, 'bm25') + .resolves([ + { + content: 'foo', + score: 5, + filePath: 'foo.java', + }, + { + content: 'bar', + score: 4, + filePath: 'bar.java', + }, + { + content: 'baz', + score: 3, + filePath: 'baz.java', + }, + { + content: 'qux', + score: 2, + filePath: 'qux.java', + }, + { + content: 'quux', + score: 1, + filePath: 'quux.java', + }, + ]) + + const actual = await crossFile.fetchSupplementalContextForSrc(myCurrentEditor, fakeCancellationToken) + assert.ok(actual) + assert.strictEqual(actual.supplementalContextItems.length, 5) + assert.strictEqual(actual?.strategy, 'bm25') + + assert.deepEqual(actual?.supplementalContextItems[0], { + content: 'foo', + score: 5, + filePath: 'foo.java', + }) + + assert.deepEqual(actual?.supplementalContextItems[1], { + content: 'bar', + score: 4, + filePath: 'bar.java', + }) + assert.deepEqual(actual?.supplementalContextItems[2], { + content: 'baz', + score: 3, + filePath: 'baz.java', + }) + + assert.deepEqual(actual?.supplementalContextItems[3], { + content: 'qux', + score: 2, + filePath: 'qux.java', + }) + + assert.deepEqual(actual?.supplementalContextItems[4], { + content: 'quux', + score: 1, + filePath: 'quux.java', }) }) }) @@ -140,12 +259,11 @@ describe('crossFileContextUtil', function () { }) }) - describe('partial support - control group', function () { + describe.skip('partial support - control group', function () { const fileExtLists: string[] = [] before(async function () { this.timeout(60000) - userGroupSettings.userGroup = UserGroup.Control }) beforeEach(async function () { @@ -172,12 +290,11 @@ describe('crossFileContextUtil', function () { }) }) - describe('partial support - crossfile group', function () { + describe.skip('partial support - crossfile group', function () { const fileExtLists: string[] = [] before(async function () { this.timeout(60000) - userGroupSettings.userGroup = UserGroup.CrossFile }) beforeEach(async function () { @@ -221,7 +338,18 @@ describe('crossFileContextUtil', function () { }) fileExtLists.forEach((fileExt) => { - it('should be non empty', async function () { + it(`supplemental context for file ${fileExt} should be non empty`, async function () { + sinon.stub(FeatureConfigProvider.instance, 'getProjectContextGroup').returns('control') + sinon + .stub(LspController.instance, 'queryInlineProjectContext') + .withArgs(sinon.match.any, sinon.match.any, 'codemap') + .resolves([ + { + content: 'foo', + score: 0, + filePath: 'q-inline', + }, + ]) const editor = await toTextEditor('content-1', `file-1.${fileExt}`, tempFolder) await toTextEditor('content-2', `file-2.${fileExt}`, tempFolder, { preview: false }) await toTextEditor('content-3', `file-3.${fileExt}`, tempFolder, { preview: false }) @@ -265,73 +393,59 @@ describe('crossFileContextUtil', function () { assert.strictEqual(chunks[1].content, 'line_6\nline_7') }) - it('codewhisperer crossfile config should use 10 lines', async function () { + it('codewhisperer crossfile config should use 50 lines', async function () { const filePath = path.join(tempFolder, 'file.txt') - await toFile(sampleFileOf60Lines, filePath) + await toFile(aStringWithLineCount(210), filePath) const chunks = await crossFile.splitFileToChunks(filePath, crossFileContextConfig.numberOfLinesEachChunk) - assert.strictEqual(chunks.length, 6) + + // (210 / 50) + 1 + assert.strictEqual(chunks.length, 5) + // line0 -> line49 + assert.strictEqual(chunks[0].content, aStringWithLineCount(50, 0)) + // line50 -> line99 + assert.strictEqual(chunks[1].content, aStringWithLineCount(50, 50)) + // line100 -> line149 + assert.strictEqual(chunks[2].content, aStringWithLineCount(50, 100)) + // line150 -> line199 + assert.strictEqual(chunks[3].content, aStringWithLineCount(50, 150)) + // line 200 -> line209 + assert.strictEqual(chunks[4].content, aStringWithLineCount(10, 200)) + }) + + it('linkChunks should add another chunk which will link to the first chunk and chunk.nextContent should reflect correct value', async function () { + const filePath = path.join(tempFolder, 'file.txt') + await toFile(aStringWithLineCount(210), filePath) + + const chunks = await crossFile.splitFileToChunks(filePath, crossFileContextConfig.numberOfLinesEachChunk) + const linkedChunks = crossFile.linkChunks(chunks) + + // 210 / 50 + 2 + assert.strictEqual(linkedChunks.length, 6) + + // 0th + assert.strictEqual(linkedChunks[0].content, aStringWithLineCount(3, 0)) + assert.strictEqual(linkedChunks[0].nextContent, aStringWithLineCount(50, 0)) + + // 1st + assert.strictEqual(linkedChunks[1].content, aStringWithLineCount(50, 0)) + assert.strictEqual(linkedChunks[1].nextContent, aStringWithLineCount(50, 50)) + + // 2nd + assert.strictEqual(linkedChunks[2].content, aStringWithLineCount(50, 50)) + assert.strictEqual(linkedChunks[2].nextContent, aStringWithLineCount(50, 100)) + + // 3rd + assert.strictEqual(linkedChunks[3].content, aStringWithLineCount(50, 100)) + assert.strictEqual(linkedChunks[3].nextContent, aStringWithLineCount(50, 150)) + + // 4th + assert.strictEqual(linkedChunks[4].content, aStringWithLineCount(50, 150)) + assert.strictEqual(linkedChunks[4].nextContent, aStringWithLineCount(10, 200)) + + // 5th + assert.strictEqual(linkedChunks[5].content, aStringWithLineCount(10, 200)) + assert.strictEqual(linkedChunks[5].nextContent, aStringWithLineCount(10, 200)) }) }) }) - -const sampleFileOf60Lines = `import java.util.List; -// we need this comment on purpose because chunk will be trimed right, adding this to avoid trimRight and make assertion easier -/** - * - * - * - * - * - **/ -class Main { - public static void main(String[] args) { - Calculator calculator = new Calculator(); - calculator.add(1, 2); - calculator.subtract(1, 2); - calculator.multiply(1, 2); - calculator.divide(1, 2); - calculator.remainder(1, 2); - } -} -// -class Calculator { - public Calculator() { - System.out.println("constructor"); - } -// - public add(int num1, int num2) { - System.out.println("add"); - return num1 + num2; - } -// - public subtract(int num1, int num2) { - System.out.println("subtract"); - return num1 - num2; - } -// - public multiply(int num1, int num2) { - System.out.println("multiply"); - return num1 * num2; - } -// - public divide(int num1, int num2) { - System.out.println("divide"); - return num1 / num2; - } -// - public remainder(int num1, int num2) { - System.out.println("remainder"); - return num1 % num2; - } -// - public power(int num1, int num2) { - System.out.println("power"); - return (int) Math.pow(num1, num2); - } -// - public squareRoot(int num1) { - System.out.println("squareRoot"); - return (int) Math.sqrt(num1); - } -}` diff --git a/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts b/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts index 2efb537ef60..653505d6cf9 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts @@ -4,7 +4,7 @@ */ import assert from 'assert' -import { resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' +import { resetCodeWhispererGlobalVariables, toTextDocument } from 'aws-core-vscode/test' import { runtimeLanguageContext, RuntimeLanguageContext, PlatformLanguageId } from 'aws-core-vscode/codewhisperer' import * as codewhispererClient from 'aws-core-vscode/codewhisperer' import { CodewhispererLanguage } from 'aws-core-vscode/shared' @@ -36,13 +36,13 @@ describe('runtimeLanguageContext', function () { ['json', true], ['yaml', true], ['tf', true], - ['dart', false], - ['lua', false], - ['powershell', false], - ['r', false], - ['swift', false], - ['systemVerilog', false], - ['vue', false], + ['dart', true], + ['lua', true], + ['powershell', true], + ['r', true], + ['swift', true], + ['systemVerilog', true], + ['vue', true], ['plaintext', false], ['html', false], ['vb', false], @@ -61,6 +61,61 @@ describe('runtimeLanguageContext', function () { assert.strictEqual(actual, expected) }) }) + + describe('test isLanguageSupported with document as the argument', function () { + const cases: [string, boolean][] = [ + ['helloJava.java', true], + ['helloPython.py', true], + ['helloJavascript.js', true], + ['helloJsx.jsx', true], + ['helloTypescript.ts', true], + ['helloTsx.tsx', true], + ['helloCsharp.cs', true], + ['helloC.c', true], + ['helloC.h', true], + ['helloCpp.cpp', true], + ['helloCpp.cc', true], + ['helloGo.go', true], + ['helloKotlin.kt', true], + ['helloPhp.php', true], + ['helloRuby.rb', true], + ['helloRust.rs', true], + ['helloScala.scala', true], + ['helloShellscript.sh', true], + ['helloSql.sql', true], + ['helloSystemVerilog.svh', true], + ['helloSystemVerilog.sv', true], + ['helloSystemVerilog.vh', true], + ['helloDart.dart', true], + ['helloLua.lua', true], + ['helloLua.wlua', true], + ['helloSwift.swift', true], + ['helloVue.vue', true], + ['helloPowerShell.ps1', true], + ['helloPowerShell.psm1', true], + ['helloR.r', true], + ['helloJson.json', true], + ['helloYaml.yaml', true], + ['helloYaml.yml', true], + ['helloTf.tf', true], + ['helloPlaintext.txt', false], + ['helloHtml.html', false], + ['helloCss.css', false], + ['helloUnknown', false], + ['helloFoo.foo', false], + ] + + cases.forEach((tuple) => { + const fileName = tuple[0] + const expected = tuple[1] + + it(`pass document ${fileName} as argument should first try determine by languageId then file extensions`, async function () { + const doc = await toTextDocument('', fileName) + const actual = languageContext.isLanguageSupported(doc) + assert.strictEqual(actual, expected) + }) + }) + }) }) describe('test getLanguageContext', function () { @@ -182,7 +237,7 @@ describe('runtimeLanguageContext', function () { }) describe('toRuntimeLanguage', function () { - const codewhispererLanguageIds: CodewhispererLanguage[][] = [ + const codewhispererLanguageIds: [CodewhispererLanguage, string][] = [ ['c', 'c'], ['cpp', 'cpp'], ['csharp', 'csharp'], @@ -201,6 +256,13 @@ describe('runtimeLanguageContext', function () { ['sql', 'sql'], ['tsx', 'typescript'], ['typescript', 'typescript'], + ['dart', 'dart'], + ['lua', 'lua'], + ['powershell', 'powershell'], + ['r', 'r'], + ['swift', 'swift'], + ['systemVerilog', 'systemverilog'], + ['vue', 'vue'], ] for (const [inputCwsprLanguageId, expectedCwsprLanguageId] of codewhispererLanguageIds) { @@ -258,7 +320,7 @@ describe('runtimeLanguageContext', function () { }) } - const arbitraryStrs: (string | undefined)[] = ['foo', undefined, 'bar', 'R', 'r', 'unknown'] + const arbitraryStrs: (string | undefined)[] = ['foo', undefined, 'bar', 'unknown'] for (const inputStr of arbitraryStrs) { it(`should return undefined when input str is ${inputStr}`, function () { const actual = languageContext.getLanguageExtensionForNotebook(inputStr) diff --git a/packages/amazonq/test/unit/codewhisperer/util/securityScanLanguageContext.test.ts b/packages/amazonq/test/unit/codewhisperer/util/securityScanLanguageContext.test.ts index 71ece8c66a9..cb0a51fdad8 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/securityScanLanguageContext.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/securityScanLanguageContext.test.ts @@ -17,20 +17,20 @@ describe('securityScanLanguageContext', function () { ['javascript', true], ['typescript', true], ['jsx', false], - ['javascriptreact', false], - ['typescriptreact', false], + ['javascriptreact', true], + ['typescriptreact', true], ['tsx', false], ['csharp', true], ['python', true], ['c', true], ['cpp', true], ['go', true], - ['kotlin', false], + ['kotlin', true], ['php', true], ['ruby', true], ['rust', false], - ['scala', false], - ['shellscript', false], + ['scala', true], + ['shellscript', true], ['sql', false], ['json', true], ['yaml', true], @@ -92,7 +92,9 @@ describe('securityScanLanguageContext', function () { ['java', 'java'], ['python', 'python'], ['javascript', 'javascript'], + ['javascriptreact', 'javascript'], ['typescript', 'typescript'], + ['typescriptreact', 'typescript'], ['csharp', 'csharp'], ['go', 'go'], ['ruby', 'ruby'], @@ -112,6 +114,9 @@ describe('securityScanLanguageContext', function () { ['java-properties', 'plaintext'], ['go.mod', 'plaintext'], ['go.sum', 'plaintext'], + ['kotlin', 'kotlin'], + ['scala', 'scala'], + ['shellscript', 'shell'], ] for (const [securityScanLanguageId, expectedCwsprLanguageId] of securityScanLanguageIds) { diff --git a/packages/amazonq/test/unit/codewhisperer/util/supplemetalContextUtil.test.ts b/packages/amazonq/test/unit/codewhisperer/util/supplemetalContextUtil.test.ts new file mode 100644 index 00000000000..051ac65bee1 --- /dev/null +++ b/packages/amazonq/test/unit/codewhisperer/util/supplemetalContextUtil.test.ts @@ -0,0 +1,86 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import * as FakeTimers from '@sinonjs/fake-timers' +import * as vscode from 'vscode' +import * as sinon from 'sinon' +import * as crossFile from 'aws-core-vscode/codewhisperer' +import { TestFolder, assertTabCount, installFakeClock } from 'aws-core-vscode/test' +import { FeatureConfigProvider } from 'aws-core-vscode/codewhisperer' +import { toTextEditor } from 'aws-core-vscode/test' +import { LspController } from 'aws-core-vscode/amazonq' + +describe('supplementalContextUtil', function () { + let testFolder: TestFolder + let clock: FakeTimers.InstalledClock + + const fakeCancellationToken: vscode.CancellationToken = { + isCancellationRequested: false, + onCancellationRequested: sinon.spy(), + } + + before(function () { + clock = installFakeClock() + }) + + after(function () { + clock.uninstall() + }) + + beforeEach(async function () { + testFolder = await TestFolder.create() + sinon.stub(FeatureConfigProvider.instance, 'getProjectContextGroup').returns('control') + }) + + afterEach(function () { + sinon.restore() + }) + + describe('fetchSupplementalContext', function () { + describe('openTabsContext', function () { + it('opentabContext should include chunks if non empty', async function () { + sinon + .stub(LspController.instance, 'queryInlineProjectContext') + .withArgs(sinon.match.any, sinon.match.any, 'codemap') + .resolves([ + { + content: 'foo', + score: 0, + filePath: 'q-inline', + }, + ]) + await toTextEditor('class Foo', 'Foo.java', testFolder.path, { preview: false }) + await toTextEditor('class Bar', 'Bar.java', testFolder.path, { preview: false }) + await toTextEditor('class Baz', 'Baz.java', testFolder.path, { preview: false }) + + const editor = await toTextEditor('public class Foo {}', 'Query.java', testFolder.path, { + preview: false, + }) + + await assertTabCount(4) + + const actual = await crossFile.fetchSupplementalContext(editor, fakeCancellationToken) + assert.ok(actual?.supplementalContextItems.length === 4) + }) + + it('opentabsContext should filter out empty chunks', async function () { + // open 3 files as supplemental context candidate files but none of them have contents + await toTextEditor('', 'Foo.java', testFolder.path, { preview: false }) + await toTextEditor('', 'Bar.java', testFolder.path, { preview: false }) + await toTextEditor('', 'Baz.java', testFolder.path, { preview: false }) + + const editor = await toTextEditor('public class Foo {}', 'Query.java', testFolder.path, { + preview: false, + }) + + await assertTabCount(4) + + const actual = await crossFile.fetchSupplementalContext(editor, fakeCancellationToken) + assert.ok(actual?.supplementalContextItems.length === 0) + }) + }) + }) +}) diff --git a/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts b/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts index d1f665cc897..d6190a6c0fd 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/telemetryHelper.test.ts @@ -5,8 +5,7 @@ import assert from 'assert' import { assertTelemetryCurried, resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' -import { TelemetryHelper, CodeWhispererUserGroupSettings, Completion, session } from 'aws-core-vscode/codewhisperer' -import * as CodeWhispererConstants from 'aws-core-vscode/codewhisperer' +import { TelemetryHelper, Completion, session } from 'aws-core-vscode/codewhisperer' import { CodewhispererCompletionType, CodewhispererSuggestionState, @@ -29,7 +28,6 @@ function aUserDecision( codewhispererSuggestionState: codewhispererSuggestionState, codewhispererTriggerType: 'OnDemand', credentialStartUrl: 'https://www.amazon.com', - codewhispererUserGroup: 'Control', } } @@ -96,7 +94,6 @@ describe('telemetryHelper', function () { beforeEach(async function () { await resetCodeWhispererGlobalVariables() sut = new TelemetryHelper() - CodeWhispererUserGroupSettings.instance.userGroup = CodeWhispererConstants.UserGroup.Control }) it('should return Line and Accept', function () { @@ -126,7 +123,6 @@ describe('telemetryHelper', function () { codewhispererSuggestionCount: 4, codewhispererSuggestionImportCount: 0, codewhispererSuggestionState: 'Accept', - codewhispererUserGroup: 'Control', codewhispererCompletionType: 'Line', codewhispererTypeaheadLength: 0, codewhispererCharactersAccepted: aCompletion().content.length, @@ -160,7 +156,6 @@ describe('telemetryHelper', function () { codewhispererSuggestionCount: 4, codewhispererSuggestionImportCount: 0, codewhispererSuggestionState: 'Accept', - codewhispererUserGroup: 'Control', codewhispererCompletionType: 'Line', codewhispererTypeaheadLength: 0, codewhispererCharactersAccepted: aCompletion().content.length, @@ -194,7 +189,6 @@ describe('telemetryHelper', function () { codewhispererSuggestionCount: 4, codewhispererSuggestionImportCount: 0, codewhispererSuggestionState: 'Reject', - codewhispererUserGroup: 'Control', codewhispererCompletionType: 'Line', codewhispererTypeaheadLength: 0, codewhispererCharactersAccepted: 0, @@ -252,13 +246,7 @@ describe('telemetryHelper', function () { await resetCodeWhispererGlobalVariables() }) - afterEach(function () { - CodeWhispererUserGroupSettings.instance.reset() - }) - it('Should call telemetry record for each recommendations with proper arguments', async function () { - CodeWhispererUserGroupSettings.instance.userGroup = CodeWhispererConstants.UserGroup.Classifier - const telemetryHelper = new TelemetryHelper() const response = [{ content: "print('Hello')" }] const requestIdList = ['test_x', 'test_x', 'test_y'] @@ -286,7 +274,6 @@ describe('telemetryHelper', function () { codewhispererSuggestionReferenceCount: 0, codewhispererCompletionType: 'Line', codewhispererLanguage: 'python', - codewhispererUserGroup: 'Classifier', }) }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/util/utgUtils.test.ts b/packages/amazonq/test/unit/codewhisperer/util/utgUtils.test.ts index b9e2789f174..67359b8a6fc 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/utgUtils.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/utgUtils.test.ts @@ -5,43 +5,32 @@ import assert from 'assert' import * as utgUtils from 'aws-core-vscode/codewhisperer' -import { UserGroup } from 'aws-core-vscode/codewhisperer' describe('shouldFetchUtgContext', () => { it('fully supported language', function () { - assert.ok(utgUtils.shouldFetchUtgContext('java', UserGroup.Control)) - assert.ok(utgUtils.shouldFetchUtgContext('java', UserGroup.CrossFile)) + assert.ok(utgUtils.shouldFetchUtgContext('java')) }) it('partially supported language', () => { - assert.strictEqual(utgUtils.shouldFetchUtgContext('python', UserGroup.Control), false) - assert.strictEqual(utgUtils.shouldFetchUtgContext('python', UserGroup.CrossFile), true) + assert.strictEqual(utgUtils.shouldFetchUtgContext('python'), false) }) it('not supported language', () => { - assert.strictEqual(utgUtils.shouldFetchUtgContext('typescript', UserGroup.Control), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('typescript', UserGroup.CrossFile), undefined) + assert.strictEqual(utgUtils.shouldFetchUtgContext('typescript'), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('javascript', UserGroup.Control), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('javascript', UserGroup.CrossFile), undefined) + assert.strictEqual(utgUtils.shouldFetchUtgContext('javascript'), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('javascriptreact', UserGroup.Control), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('javascriptreact', UserGroup.CrossFile), undefined) + assert.strictEqual(utgUtils.shouldFetchUtgContext('javascriptreact'), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('typescriptreact', UserGroup.Control), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('typescriptreact', UserGroup.CrossFile), undefined) + assert.strictEqual(utgUtils.shouldFetchUtgContext('typescriptreact'), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('scala', UserGroup.Control), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('scala', UserGroup.CrossFile), undefined) + assert.strictEqual(utgUtils.shouldFetchUtgContext('scala'), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('shellscript', UserGroup.Control), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('shellscript', UserGroup.CrossFile), undefined) + assert.strictEqual(utgUtils.shouldFetchUtgContext('shellscript'), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('csharp', UserGroup.Control), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('csharp', UserGroup.CrossFile), undefined) + assert.strictEqual(utgUtils.shouldFetchUtgContext('csharp'), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('c', UserGroup.Control), undefined) - assert.strictEqual(utgUtils.shouldFetchUtgContext('c', UserGroup.CrossFile), undefined) + assert.strictEqual(utgUtils.shouldFetchUtgContext('c'), undefined) }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/util/zipUtil.test.ts b/packages/amazonq/test/unit/codewhisperer/util/zipUtil.test.ts index d7fd10242a4..ee4abafa19a 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/zipUtil.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/zipUtil.test.ts @@ -7,21 +7,31 @@ import assert from 'assert' import vscode from 'vscode' import sinon from 'sinon' import { join } from 'path' -import { getTestWorkspaceFolder } from 'aws-core-vscode/testInteg' +import { getTestWorkspaceFolder } from 'aws-core-vscode/test' import { CodeAnalysisScope, ZipUtil } from 'aws-core-vscode/codewhisperer' import { codeScanTruncDirPrefix } from 'aws-core-vscode/codewhisperer' import { ToolkitError } from 'aws-core-vscode/shared' +import { LspClient } from 'aws-core-vscode/amazonq' +import { fs } from 'aws-core-vscode/shared' +import path from 'path' +import JSZip from 'jszip' describe('zipUtil', function () { const workspaceFolder = getTestWorkspaceFolder() const appRoot = join(workspaceFolder, 'java11-plain-maven-sam-app') const appCodePath = join(appRoot, 'HelloWorldFunction', 'src', 'main', 'java', 'helloworld', 'App.java') + const appCodePathWithRepeatedProjectName = join(workspaceFolder, 'workspaceFolder', 'App.java') describe('getProjectPaths', function () { it('Should return the correct project paths', function () { const zipUtil = new ZipUtil() assert.deepStrictEqual(zipUtil.getProjectPaths(), [workspaceFolder]) }) + + it('Should return the correct project path for unit test generation', function () { + const zipUtil = new ZipUtil() + assert.deepStrictEqual(zipUtil.getProjectPath(appCodePath), workspaceFolder) + }) }) describe('generateZip', function () { @@ -34,7 +44,7 @@ describe('zipUtil', function () { }) it('Should generate zip for file scan and return expected metadata', async function () { - const zipMetadata = await zipUtil.generateZip(vscode.Uri.file(appCodePath), CodeAnalysisScope.FILE) + const zipMetadata = await zipUtil.generateZip(vscode.Uri.file(appCodePath), CodeAnalysisScope.FILE_AUTO) assert.strictEqual(zipMetadata.lines, 49) assert.ok(zipMetadata.rootDir.includes(codeScanTruncDirPrefix)) assert.ok(zipMetadata.srcPayloadSizeInBytes > 0) @@ -48,7 +58,7 @@ describe('zipUtil', function () { sinon.stub(zipUtil, 'reachSizeLimit').returns(true) await assert.rejects( - () => zipUtil.generateZip(vscode.Uri.file(appCodePath), CodeAnalysisScope.FILE), + () => zipUtil.generateZip(vscode.Uri.file(appCodePath), CodeAnalysisScope.FILE_AUTO), new ToolkitError(`Payload size limit reached`, { code: 'FileSizeExceeded' }) ) }) @@ -104,5 +114,158 @@ describe('zipUtil', function () { ) assert.equal(zipMetadata2.lines, zipMetadata.lines + 1) }) + + it('should handle path with repeated project name for file scan', async function () { + const zipMetadata = await zipUtil.generateZip( + vscode.Uri.file(appCodePathWithRepeatedProjectName), + CodeAnalysisScope.FILE_ON_DEMAND + ) + + const zipFileData = await fs.readFileBytes(zipMetadata.zipFilePath) + const zip = await JSZip.loadAsync(zipFileData) + const files = Object.keys(zip.files) + assert.ok(files.includes(join('workspaceFolder', 'workspaceFolder', 'App.java'))) + }) + + it('should handle path with repeated project name for project scan', async function () { + const zipMetadata = await zipUtil.generateZip( + vscode.Uri.file(appCodePathWithRepeatedProjectName), + CodeAnalysisScope.PROJECT + ) + + const zipFileData = await fs.readFileBytes(zipMetadata.zipFilePath) + const zip = await JSZip.loadAsync(zipFileData) + const files = Object.keys(zip.files) + assert.ok(files.includes(join('workspaceFolder', 'workspaceFolder', 'App.java'))) + }) + }) + + describe('generateZipTestGen', function () { + let zipUtil: ZipUtil + let mockFs: sinon.SinonStubbedInstance + const projectPath = '/test/project' + const zipDirPath = '/test/zip' + const zipFilePath = '/test/zip/test.zip' + + beforeEach(function () { + zipUtil = new ZipUtil() + mockFs = sinon.stub(fs) + + const mockRepoMapPath = '/path/to/repoMapData.json' + mockFs.exists.withArgs(mockRepoMapPath).resolves(true) + sinon.stub(LspClient, 'instance').get(() => ({ + getRepoMapJSON: sinon.stub().resolves(mockRepoMapPath), + })) + + sinon.stub(zipUtil, 'getZipDirPath').returns(zipDirPath) + sinon.stub(zipUtil as any, 'zipProject').resolves(zipFilePath) + }) + + afterEach(function () { + sinon.restore() + }) + + it('Should generate zip for test generation successfully', async function () { + mockFs.stat.resolves({ + type: vscode.FileType.File, + size: 1000, + ctime: Date.now(), + mtime: Date.now(), + } as vscode.FileStat) + + mockFs.readFileBytes.resolves(Buffer.from('test content')) + + // Fix: Create a Set from the array + zipUtil['_totalSize'] = 500 + zipUtil['_totalBuildSize'] = 200 + zipUtil['_totalLines'] = 100 + zipUtil['_language'] = 'typescript' + zipUtil['_pickedSourceFiles'] = new Set(['file1.ts', 'file2.ts']) + + const result = await zipUtil.generateZipTestGen(projectPath, false) + + assert.ok(mockFs.mkdir.calledWith(path.join(zipDirPath, 'utgRequiredArtifactsDir'))) + assert.ok( + mockFs.mkdir.calledWith(path.join(zipDirPath, 'utgRequiredArtifactsDir', 'buildAndExecuteLogDir')) + ) + assert.ok(mockFs.mkdir.calledWith(path.join(zipDirPath, 'utgRequiredArtifactsDir', 'repoMapData'))) + assert.ok(mockFs.mkdir.calledWith(path.join(zipDirPath, 'utgRequiredArtifactsDir', 'testCoverageDir'))) + + // assert.ok( + // mockFs.copy.calledWith( + // '/path/to/repoMapData.json', + // path.join(zipDirPath, 'utgRequiredArtifactsDir', 'repoMapData', 'repoMapData.json') + // ) + // ) + + assert.strictEqual(result.rootDir, zipDirPath) + assert.strictEqual(result.zipFilePath, zipFilePath) + assert.strictEqual(result.srcPayloadSizeInBytes, 500) + assert.strictEqual(result.buildPayloadSizeInBytes, 200) + assert.strictEqual(result.zipFileSizeInBytes, 1000) + assert.strictEqual(result.lines, 100) + assert.strictEqual(result.language, 'typescript') + assert.deepStrictEqual(Array.from(result.scannedFiles), ['file1.ts', 'file2.ts']) + }) + + // it('Should handle LSP client error', async function () { + // // Override the default stub with one that rejects + // sinon.stub(LspClient, 'instance').get(() => ({ + // getRepoMapJSON: sinon.stub().rejects(new Error('LSP error')), + // })) + + // await assert.rejects(() => zipUtil.generateZipTestGen(projectPath), /LSP error/) + // }) + + it('Should handle file system errors during directory creation', async function () { + sinon.stub(LspClient, 'instance').get(() => ({ + getRepoMapJSON: sinon.stub().resolves('{"mock": "data"}'), + })) + mockFs.mkdir.rejects(new Error('Directory creation failed')) + + await assert.rejects(() => zipUtil.generateZipTestGen(projectPath, false), /Directory creation failed/) + }) + + it('Should handle zip project errors', async function () { + sinon.stub(LspClient, 'instance').get(() => ({ + getRepoMapJSON: sinon.stub().resolves('{"mock": "data"}'), + })) + ;(zipUtil as any).zipProject.rejects(new Error('Zip failed')) + + await assert.rejects(() => zipUtil.generateZipTestGen(projectPath, false), /Zip failed/) + }) + + it('Should handle file copy to downloads folder error', async function () { + // Mock LSP client + sinon.stub(LspClient, 'instance').get(() => ({ + getRepoMapJSON: sinon.stub().resolves('{"mock": "data"}'), + })) + + // Mock file operations + const mockFs = { + mkdir: sinon.stub().resolves(), + copy: sinon.stub().rejects(new Error('Copy failed')), + exists: sinon.stub().resolves(true), + stat: sinon.stub().resolves({ + type: vscode.FileType.File, + size: 1000, + ctime: Date.now(), + mtime: Date.now(), + } as vscode.FileStat), + } + + // Since the function now uses Promise.all for directory creation and file operations, + // we need to ensure the mkdir succeeds but the copy fails + fs.mkdir = mockFs.mkdir + fs.copy = mockFs.copy + fs.exists = mockFs.exists + fs.stat = mockFs.stat + + await assert.rejects(() => zipUtil.generateZipTestGen(projectPath, false), /Copy failed/) + + // Verify mkdir was called for all directories + assert(mockFs.mkdir.called, 'mkdir should have been called') + assert.strictEqual(mockFs.mkdir.callCount, 4, 'mkdir should have been called 4 times') + }) }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/views/securityPanelViewProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/views/securityPanelViewProvider.test.ts index 0b48eac2106..ca163e86208 100644 --- a/packages/amazonq/test/unit/codewhisperer/views/securityPanelViewProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/views/securityPanelViewProvider.test.ts @@ -27,6 +27,9 @@ const codeScanIssue: CodeScanIssue[] = [ severity: 'low', recommendation: { text: 'foo', url: 'foo' }, suggestedFixes: [], + visible: true, + language: 'python', + scanJobId: 'scanJob', }, ] diff --git a/packages/amazonq/test/unit/index.ts b/packages/amazonq/test/unit/index.ts index 982ac5bac1f..cc1ad0d22ca 100644 --- a/packages/amazonq/test/unit/index.ts +++ b/packages/amazonq/test/unit/index.ts @@ -7,9 +7,7 @@ import { runTests } from 'aws-core-vscode/test' import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils' export function run(): Promise { - return runTests( - process.env.TEST_DIR ?? 'test/unit', - ['../../core/dist/src/test/globalSetup.test.ts'], - VSCODE_EXTENSION_ID.amazonq - ) + return runTests(process.env.TEST_DIR ?? ['test/unit'], VSCODE_EXTENSION_ID.amazonq, [ + '../../core/dist/src/test/globalSetup.test.ts', + ]) } diff --git a/packages/amazonq/test/unit/validation.test.ts b/packages/amazonq/test/unit/validation.test.ts index 6277712da17..4b19d94935a 100644 --- a/packages/amazonq/test/unit/validation.test.ts +++ b/packages/amazonq/test/unit/validation.test.ts @@ -23,7 +23,7 @@ describe('package validations', function () { */ it('has synced contributes.icons with core/package.json', async function () { const corePackageJson = JSON.parse( - await fs.readFileAsString(path.resolve(__dirname, '../../../../core/package.json')) + await fs.readFileText(path.resolve(__dirname, '../../../../core/package.json')) ) assert.deepStrictEqual(packageJson.contributes.icons, corePackageJson.contributes.icons) }) diff --git a/packages/core/src/testWeb/extension.test.ts b/packages/amazonq/test/web/extension.test.ts similarity index 64% rename from packages/core/src/testWeb/extension.test.ts rename to packages/amazonq/test/web/extension.test.ts index eb1d7eb22aa..de685bf10f7 100644 --- a/packages/core/src/testWeb/extension.test.ts +++ b/packages/amazonq/test/web/extension.test.ts @@ -4,10 +4,10 @@ */ import assert from 'assert' -import globals from '../shared/extensionGlobals' +import { globals, ToolkitGlobals } from 'aws-core-vscode/shared' describe('activation', async () => { it('defines a region provider that can provide regions when in web mode', async () => { - assert(globals.regionProvider.getRegions().length > 0) + assert((globals as unknown as ToolkitGlobals).regionProvider.getRegions().length > 0) }) }) diff --git a/packages/core/src/testWeb/shared/crypto.test.ts b/packages/amazonq/test/web/shared/crypto.test.ts similarity index 94% rename from packages/core/src/testWeb/shared/crypto.test.ts rename to packages/amazonq/test/web/shared/crypto.test.ts index 6f64f4d0214..01da83cdf25 100644 --- a/packages/core/src/testWeb/shared/crypto.test.ts +++ b/packages/amazonq/test/web/shared/crypto.test.ts @@ -4,7 +4,7 @@ */ import assert from 'assert' -import { randomUUID } from '../../shared/crypto' +import { randomUUID } from 'aws-core-vscode/shared' describe('crypto', function () { describe('randomUUID()', function () { diff --git a/packages/core/src/testWeb/shared/fs.test.ts b/packages/amazonq/test/web/shared/fs.test.ts similarity index 88% rename from packages/core/src/testWeb/shared/fs.test.ts rename to packages/amazonq/test/web/shared/fs.test.ts index 1251ecf053f..df3e677b287 100644 --- a/packages/core/src/testWeb/shared/fs.test.ts +++ b/packages/amazonq/test/web/shared/fs.test.ts @@ -4,8 +4,7 @@ */ import assert from 'assert' -import globals from '../../shared/extensionGlobals' -import fs from '../../shared/fs/fs' +import { fs, globals } from 'aws-core-vscode/shared' describe('FileSystem', function () { it('getUserHomeDir()', async () => { diff --git a/packages/core/src/testWeb/shared/utilities/timeoutUtils.test.ts b/packages/amazonq/test/web/shared/utilities/timeoutUtils.test.ts similarity index 79% rename from packages/core/src/testWeb/shared/utilities/timeoutUtils.test.ts rename to packages/amazonq/test/web/shared/utilities/timeoutUtils.test.ts index 1496345f46e..1885c8918d0 100644 --- a/packages/core/src/testWeb/shared/utilities/timeoutUtils.test.ts +++ b/packages/amazonq/test/web/shared/utilities/timeoutUtils.test.ts @@ -9,5 +9,5 @@ * * Any web specific tests should be made within their own `describe()`. */ -import { timeoutUtilsDescribe } from '../../../test/shared/utilities/timeoutUtils.test' +import { timeoutUtilsDescribe } from 'aws-core-vscode/testWeb' timeoutUtilsDescribe diff --git a/packages/core/src/testWeb/testRunner.ts b/packages/amazonq/test/web/testRunner.ts similarity index 78% rename from packages/core/src/testWeb/testRunner.ts rename to packages/amazonq/test/web/testRunner.ts index e120e096440..a2c8f8e90cc 100644 --- a/packages/core/src/testWeb/testRunner.ts +++ b/packages/amazonq/test/web/testRunner.ts @@ -7,12 +7,12 @@ * The following was influenced by this guide: https://code.visualstudio.com/api/extension-guides/web-extensions */ +import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils' import 'mocha' // Imports mocha for the browser, defining the `mocha` global. import * as vscode from 'vscode' -import { VSCODE_EXTENSION_ID } from '../shared/extensions' export async function run(): Promise { - await activateToolkitExtension() + await activateExtension() return new Promise(async (resolve, reject) => { setupMocha() gatherTestFiles() @@ -45,8 +45,13 @@ function gatherTestFiles() { * * So this function ensures the extension has fully activated. */ -async function activateToolkitExtension() { - await vscode.extensions.getExtension(VSCODE_EXTENSION_ID.awstoolkitcore)?.activate() +async function activateExtension() { + const extId = VSCODE_EXTENSION_ID.amazonq + const ext = vscode.extensions.getExtension(extId) + if (!ext) { + throw new Error(`Extension '${extId}' not found, can't activate it to run tests.`) + } + await vscode.extensions.getExtension(VSCODE_EXTENSION_ID.amazonq)?.activate() } function runMochaTests(resolve: (value: void | PromiseLike) => void, reject: (reason?: any) => void) { diff --git a/packages/core/src/testWeb/utils.test.ts b/packages/amazonq/test/web/utils.test.ts similarity index 88% rename from packages/core/src/testWeb/utils.test.ts rename to packages/amazonq/test/web/utils.test.ts index b1f64523acc..30965ffb500 100644 --- a/packages/core/src/testWeb/utils.test.ts +++ b/packages/amazonq/test/web/utils.test.ts @@ -4,7 +4,7 @@ */ import * as assert from 'assert' -import { isWeb } from '../shared/extensionGlobals' +import { isWeb } from 'aws-core-vscode/shared' describe('isWeb', function () { it('returns true when in web mode', function () { diff --git a/packages/amazonq/webpack.config.js b/packages/amazonq/webpack.config.js index f606cd0517a..44e01684754 100644 --- a/packages/amazonq/webpack.config.js +++ b/packages/amazonq/webpack.config.js @@ -18,6 +18,7 @@ module.exports = (env, argv) => { ...baseWebConfigsFactory(env, argv), entry: { 'src/extensionWeb': './src/extensionWeb.ts', + 'test/web/testRunnerWebCore': './test/web/testRunner.ts', }, } diff --git a/packages/core/.c8rc.json b/packages/core/.c8rc.json deleted file mode 100644 index 26b94d764c1..00000000000 --- a/packages/core/.c8rc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "report-dir": "../../coverage/core", - "reporter": ["lcov"], - "all": true, - "include": ["src/**", "dist/src/**"], - "exclude": [ - "dist/src/ssmDocument/ssm/ssmServer.js", - "dist/src/test/**", - "dist/src/testE2E/**", - "dist/src/testLint/**", - "dist/src/testInteg/**", - "dist/src/testFixtures/**", - "src/ssmDocument/ssm/ssmServer.js", - "src/test/**", - "src/testE2E/**", - "src/testLint/**", - "src/testInteg/**", - "src/testFixtures/**" - ] -} diff --git a/packages/core/.vscode/launch.json b/packages/core/.vscode/launch.json index 61ae90f5470..ac06b7c2550 100644 --- a/packages/core/.vscode/launch.json +++ b/packages/core/.vscode/launch.json @@ -5,138 +5,6 @@ { "version": "0.2.0", "configurations": [ - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--disable-extensions", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/dist/src/test/index", - "${workspaceRoot}/dist/src/testFixtures/workspaceFolder" - ], - "env": { - "DEVELOPMENT_PATH": "${workspaceFolder}", - "AWS_TOOLKIT_AUTOMATION": "local" - }, - "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "preLaunchTask": "build", - "presentation": { - "group": "3_ExtensionTests", - "order": 1 - } - }, - { - "name": "Extension Tests (current file)", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--disable-extensions", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/dist/src/test/index.js", - "${workspaceRoot}/dist/src/testFixtures/workspaceFolder" - ], - "env": { - "TEST_FILE": "${relativeFile}", - "DEVELOPMENT_PATH": "${workspaceFolder}", - "AWS_TOOLKIT_AUTOMATION": "local" - }, - "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "preLaunchTask": "build", - "presentation": { - "group": "2_ExtensionTestsCurrentFile", - "order": 1 - } - }, - { - "name": "Extension Tests (web)", - "type": "extensionHost", - "debugWebWorkerHost": true, - "request": "launch", - "args": [ - "--disable-extension=amazonwebservices.aws-toolkit-vscode", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionDevelopmentKind=web", - "--extensionTestsPath=${workspaceFolder}/dist/src/testWeb/testRunnerWebCore", - "${workspaceRoot}/dist/src/testFixtures/workspaceFolder" - ], - "outFiles": ["${workspaceFolder}/dist/src/**/*.js"], - "preLaunchTask": "testsBuildWatch", - "presentation": { - "group": "3_ExtensionTests", - "order": 3 - } - }, - { - "name": "Integration Tests", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--disable-extension=amazonwebservices.aws-toolkit-vscode", - "${workspaceFolder}/dist/src/testFixtures/workspaceFolder", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/dist/src/testInteg/index.js" - ], - "env": { - "DEVELOPMENT_PATH": "${workspaceFolder}", - "AWS_TOOLKIT_AUTOMATION": "local" - }, - "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "preLaunchTask": "build", - "presentation": { - "group": "6_IntegrationTests", - "order": 1 - } - }, - { - "name": "Integration Tests (current file)", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--disable-extension=amazonwebservices.aws-toolkit-vscode", - "${workspaceFolder}/dist/src/testFixtures/workspaceFolder", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/dist/src/testInteg/index.js" - ], - "env": { - "TEST_FILE": "${relativeFile}", - "DEVELOPMENT_PATH": "${workspaceFolder}", - "AWS_TOOLKIT_AUTOMATION": "local" - }, - "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "preLaunchTask": "build", - "presentation": { - "group": "5_IntegrationTestsCurrentFile", - "order": 1 - } - }, - { - "name": "E2E Test (current file)", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--disable-extension=amazonwebservices.aws-toolkit-vscode", - "${workspaceFolder}/dist/src/testFixtures/workspaceFolder", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/dist/src/testE2E/index.js" - ], - "env": { - "TEST_FILE": "${relativeFile}", - "DEVELOPMENT_PATH": "${workspaceFolder}", - "AWS_TOOLKIT_AUTOMATION": "local" - }, - "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "preLaunchTask": "build", - "presentation": { - "group": "4_E2ETestCurrentFile", - "order": 1 - } - }, { "name": "Test Lint", "type": "node", diff --git a/packages/core/.vscode/tasks.json b/packages/core/.vscode/tasks.json index 850565e29a1..1ed850a21b7 100644 --- a/packages/core/.vscode/tasks.json +++ b/packages/core/.vscode/tasks.json @@ -22,14 +22,6 @@ "isBackground": true, "problemMatcher": "$ts-webpack-watch" }, - { - "label": "testsBuildWatch", - "command": "npm run compileDev -- --watch", - "detail": "Build that is sufficient for Web mode tests", - "type": "shell", - "isBackground": true, - "problemMatcher": "$ts-webpack-watch" - }, /** After we stop debugging our browser, we also want to stop the web server. When this task is ran it will stop the web server. diff --git a/packages/core/package.json b/packages/core/package.json index 7fa00eeb0ac..23aef282444 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,30 +2,11 @@ "name": "aws-core-vscode", "description": "Core library used AWS IDE extensions for VSCode.", "version": "1.0.0", - "extensionKind": [ - "workspace" - ], - "publisher": "amazonwebservices", "license": "Apache-2.0", "engines": { "npm": "^10.1.0", - "vscode": "^1.68.0" + "vscode": "^1.83.0" }, - "activationEvents": [ - "onStartupFinished", - "onUri", - "onDebugResolve:aws-sam", - "onDebugInitialConfigurations", - "onLanguage:javascript", - "onLanguage:java", - "onLanguage:python", - "onLanguage:csharp", - "onLanguage:yaml", - "onFileSystem:s3", - "onFileSystem:s3-readonly" - ], - "main": "./dist/src/extensionNode.js", - "browser": "./dist/src/extensionWebCore.js", "exports": { ".": "./dist/src/extension.js", "./node": "./dist/src/extensionNode.js", @@ -39,4017 +20,399 @@ "./auth": "./dist/src/auth/index.js", "./amazonqGumby": "./dist/src/amazonqGumby/index.js", "./amazonqFeatureDev": "./dist/src/amazonqFeatureDev/index.js", + "./amazonqDoc": "./dist/src/amazonqDoc/index.js", + "./amazonqScan": "./dist/src/amazonqScan/index.js", + "./amazonqTest": "./dist/src/amazonqTest/index.js", "./codewhispererChat": "./dist/src/codewhispererChat/index.js", "./test": "./dist/src/test/index.js", - "./testInteg": "./dist/src/testInteg/index.js", + "./testWeb": "./dist/src/testWeb/index.js", "./login": "./dist/src/login/webview/index.js", "./utils": "./dist/src/shared/utilities/index.js", "./feedback": "./dist/src/feedback/index.js", "./telemetry": "./dist/src/shared/telemetry/index.js", - "./dev": "./dist/src/dev/index.js" + "./dev": "./dist/src/dev/index.js", + "./notifications": "./dist/src/notifications/index.js" }, "contributes": { - "configuration": { - "type": "object", - "title": "%AWS.productName%", - "cloud9": { - "cn": { - "title": "%AWS.productName.cn%" - } - }, - "properties": { - "aws.profile": { - "type": "string", - "deprecationMessage": "The current profile is now stored internally by the Toolkit.", - "description": "%AWS.configuration.profileDescription%" - }, - "aws.ecs.openTerminalCommand": { - "type": "string", - "default": "/bin/sh", - "markdownDescription": "%AWS.configuration.description.ecs.openTerminalCommand%" - }, - "aws.iot.maxItemsPerPage": { - "type": "number", - "default": 100, - "minimum": 1, - "maximum": 250, - "markdownDescription": "%AWS.configuration.description.iot.maxItemsPerPage%" - }, - "aws.s3.maxItemsPerPage": { - "type": "number", - "default": 300, - "minimum": 3, - "maximum": 1000, - "markdownDescription": "%AWS.configuration.description.s3.maxItemsPerPage%" - }, - "aws.samcli.location": { - "type": "string", - "scope": "machine", - "default": "", - "markdownDescription": "%AWS.configuration.description.samcli.location%" - }, - "aws.samcli.lambdaTimeout": { - "type": "number", - "default": 90000, - "markdownDescription": "%AWS.configuration.description.samcli.lambdaTimeout%" - }, - "aws.samcli.legacyDeploy": { - "type": "boolean", - "default": false, - "markdownDescription": "%AWS.configuration.description.samcli.legacyDeploy%" - }, - "aws.telemetry": { - "type": "boolean", - "default": true, - "markdownDescription": "%AWS.configuration.description.telemetry%", - "cloud9": { - "cn": { - "markdownDescription": "%AWS.configuration.description.telemetry.cn%" - } - } - }, - "aws.stepfunctions.asl.format.enable": { - "type": "boolean", - "scope": "window", - "default": true, - "description": "%AWS.stepFunctions.asl.format.enable.desc%" - }, - "aws.stepfunctions.asl.maxItemsComputed": { - "type": "number", - "default": 5000, - "description": "%AWS.stepFunctions.asl.maxItemsComputed.desc%" - }, - "aws.ssmDocument.ssm.maxItemsComputed": { - "type": "number", - "default": 5000, - "description": "%AWS.ssmDocument.ssm.maxItemsComputed.desc%" - }, - "aws.cwl.limit": { - "type": "number", - "default": 10000, - "description": "%AWS.cwl.limit.desc%", - "maximum": 10000 - }, - "aws.samcli.manuallySelectedBuckets": { - "type": "object", - "description": "%AWS.samcli.deploy.bucket.recentlyUsed%", - "default": [] - }, - "aws.samcli.enableCodeLenses": { - "type": "boolean", - "description": "%AWS.configuration.enableCodeLenses%", - "default": false - }, - "aws.suppressPrompts": { - "type": "object", - "description": "%AWS.configuration.description.suppressPrompts%", - "default": {}, - "properties": { - "apprunnerNotifyPricing": { - "type": "boolean", - "default": false - }, - "apprunnerNotifyPause": { - "type": "boolean", - "default": false - }, - "ecsRunCommand": { - "type": "boolean", - "default": false - }, - "ecsRunCommandEnable": { - "type": "boolean", - "default": false - }, - "ecsRunCommandDisable": { - "type": "boolean", - "default": false - }, - "regionAddAutomatically": { - "type": "boolean", - "default": false - }, - "yamlExtPrompt": { - "type": "boolean", - "default": false - }, - "fileViewerEdit": { - "type": "boolean", - "default": false - }, - "createCredentialsProfile": { - "type": "boolean", - "default": false - }, - "samcliConfirmDevStack": { - "type": "boolean", - "default": false - }, - "remoteConnected": { - "type": "boolean", - "default": false - }, - "codeCatalystConnectionExpired": { - "type": "boolean", - "default": false - }, - "ssoCacheError": { - "type": "boolean", - "default": false - } - }, - "additionalProperties": false - }, - "aws.experiments": { - "type": "object", - "markdownDescription": "%AWS.configuration.description.experiments%", - "default": { - "jsonResourceModification": false - }, - "properties": { - "jsonResourceModification": { - "type": "boolean", - "default": false - } - }, - "additionalProperties": false - }, - "aws.resources.enabledResources": { - "type": "array", - "description": "%AWS.configuration.description.resources.enabledResources%", - "items": { - "type": "string" - } - }, - "aws.lambda.recentlyUploaded": { - "type": "object", - "description": "%AWS.configuration.description.lambda.recentlyUploaded%", - "default": [] - }, - "aws.accessAnalyzer.policyChecks.checkNoNewAccessFilePath": { - "type": "string", - "default": "", - "description": "File path or S3 path to a text document for CheckNoNewAccess custom check.", - "scope": "window" - }, - "aws.accessAnalyzer.policyChecks.checkAccessNotGrantedFilePath": { - "type": "string", - "default": "", - "description": "File path or S3 path to a text document for CheckAccessNotGranted custom check.", - "scope": "window" - }, - "aws.accessAnalyzer.policyChecks.cloudFormationParameterFilePath": { - "type": "string", - "default": "", - "description": "A JSON formatted file that specifies template parameter values, a stack policy, and tags. Only parameters are used from this file.", - "scope": "machine-overridable" - }, - "amazonQ.telemetry": { - "type": "boolean", - "default": true, - "markdownDescription": "%AWS.configuration.description.amazonq.telemetry%" - }, - "amazonQ.suppressPrompts": { - "type": "object", - "description": "%AWS.configuration.description.suppressPrompts%", - "default": {}, - "properties": { - "createCredentialsProfile": { - "type": "boolean", - "default": false - }, - "codeWhispererNewWelcomeMessage": { - "type": "boolean", - "default": false - }, - "codeWhispererConnectionExpired": { - "type": "boolean", - "default": false - }, - "amazonQWelcomePage": { - "type": "boolean", - "default": false - }, - "amazonQSessionConfigurationMessage": { - "type": "boolean", - "default": false - }, - "ssoCacheError": { - "type": "boolean", - "default": false - } - }, - "additionalProperties": false - }, - "amazonQ.showInlineCodeSuggestionsWithCodeReferences": { - "type": "boolean", - "markdownDescription": "%AWS.configuration.description.amazonq%", - "default": true - }, - "amazonQ.importRecommendationForInlineCodeSuggestions": { - "type": "boolean", - "description": "%AWS.configuration.description.amazonq.importRecommendation%", - "default": true - }, - "amazonQ.shareContentWithAWS": { - "type": "boolean", - "markdownDescription": "%AWS.configuration.description.amazonq.shareContentWithAWS%", - "default": true, - "scope": "application" - }, - "amazonQ.workspaceIndex": { - "type": "boolean", - "markdownDescription": "%AWS.configuration.description.amazonq.workspaceIndex%", - "default": false, - "scope": "application" - }, - "amazonQ.workspaceIndexWorkerThreads": { - "type": "number", - "markdownDescription": "%AWS.configuration.description.amazonq.workspaceIndexWorkerThreads%", - "default": 0, - "scope": "application" - }, - "amazonQ.workspaceIndexUseGPU": { - "type": "boolean", - "markdownDescription": "%AWS.configuration.description.amazonq.workspaceIndexUseGPU%", - "default": false, - "scope": "application" - }, - "amazonQ.workspaceIndexMaxSize": { - "type": "number", - "markdownDescription": "%AWS.configuration.description.amazonq.workspaceIndexMaxSize%", - "default": 250, - "scope": "application" - } - } - }, - "debuggers": [ - { - "type": "aws-sam", - "when": "isCloud9 || !aws.isWebExtHost", - "label": "%AWS.configuration.description.awssam.debug.label%", - "configurationAttributes": { - "direct-invoke": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AwsSamDebuggerConfiguration", - "additionalProperties": false, - "properties": { - "aws": { - "title": "AWS Connection", - "description": "%AWS.configuration.description.awssam.debug.aws%", - "properties": { - "credentials": { - "description": "%AWS.configuration.description.awssam.debug.credentials%", - "type": "string", - "cloud9": { - "cn": { - "description": "%AWS.configuration.description.awssam.debug.credentials.cn%" - } - } - }, - "region": { - "description": "%AWS.configuration.description.awssam.debug.region%", - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, - "invokeTarget": { - "oneOf": [ - { - "title": "Template Target Properties", - "description": "%AWS.configuration.description.awssam.debug.invokeTarget%", - "properties": { - "templatePath": { - "description": "%AWS.configuration.description.awssam.debug.templatePath%", - "type": "string" - }, - "logicalId": { - "description": "%AWS.configuration.description.awssam.debug.logicalId%", - "type": "string" - }, - "target": { - "description": "%AWS.configuration.description.awssam.debug.target%", - "type": "string", - "enum": [ - "template" - ] - } - }, - "additionalProperties": false, - "required": [ - "templatePath", - "logicalId", - "target" - ], - "type": "object" - }, - { - "title": "Code Target Properties", - "description": "%AWS.configuration.description.awssam.debug.invokeTarget%", - "properties": { - "lambdaHandler": { - "description": "%AWS.configuration.description.awssam.debug.lambdaHandler%", - "type": "string" - }, - "projectRoot": { - "description": "%AWS.configuration.description.awssam.debug.projectRoot%", - "type": "string" - }, - "target": { - "description": "%AWS.configuration.description.awssam.debug.target%", - "type": "string", - "enum": [ - "code" - ] - }, - "architecture": { - "description": "%AWS.configuration.description.awssam.debug.architecture%", - "type": "string", - "enum": [ - "x86_64", - "arm64" - ] - } - }, - "additionalProperties": false, - "required": [ - "lambdaHandler", - "projectRoot", - "target" - ], - "type": "object" - }, - { - "title": "API Target Properties", - "description": "%AWS.configuration.description.awssam.debug.invokeTarget%", - "properties": { - "templatePath": { - "description": "%AWS.configuration.description.awssam.debug.templatePath%", - "type": "string" - }, - "logicalId": { - "description": "%AWS.configuration.description.awssam.debug.logicalId%", - "type": "string" - }, - "target": { - "description": "%AWS.configuration.description.awssam.debug.target%", - "type": "string", - "enum": [ - "api" - ] - } - }, - "additionalProperties": false, - "required": [ - "templatePath", - "logicalId", - "target" - ], - "type": "object" - } - ] - }, - "lambda": { - "title": "Lambda Properties", - "description": "%AWS.configuration.description.awssam.debug.lambda%", - "properties": { - "environmentVariables": { - "description": "%AWS.configuration.description.awssam.debug.envvars%", - "additionalProperties": { - "type": [ - "string" - ] - }, - "type": "object" - }, - "payload": { - "description": "%AWS.configuration.description.awssam.debug.event%", - "properties": { - "json": { - "description": "%AWS.configuration.description.awssam.debug.event.json%", - "type": "object" - }, - "path": { - "description": "%AWS.configuration.description.awssam.debug.event.path%", - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, - "memoryMb": { - "description": "%AWS.configuration.description.awssam.debug.memoryMb%", - "type": "number" - }, - "runtime": { - "description": "%AWS.configuration.description.awssam.debug.runtime%", - "type": "string" - }, - "timeoutSec": { - "description": "%AWS.configuration.description.awssam.debug.timeout%", - "type": "number" - }, - "pathMappings": { - "type:": "array", - "items": { - "title": "Path Mapping", - "type": "object", - "properties": { - "localRoot": { - "type": "string" - }, - "remoteRoot": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "localRoot", - "remoteRoot" - ] - } - } - }, - "additionalProperties": false, - "type": "object" - }, - "sam": { - "title": "SAM CLI Properties", - "description": "%AWS.configuration.description.awssam.debug.sam%", - "properties": { - "buildArguments": { - "description": "%AWS.configuration.description.awssam.debug.buildArguments%", - "type": "array", - "items": { - "type": "string" - } - }, - "buildDir": { - "description": "%AWS.configuration.description.awssam.debug.buildDir%", - "type": "string" - }, - "containerBuild": { - "description": "%AWS.configuration.description.awssam.debug.containerBuild%", - "type": "boolean" - }, - "dockerNetwork": { - "description": "%AWS.configuration.description.awssam.debug.dockerNetwork%", - "type": "string" - }, - "localArguments": { - "description": "%AWS.configuration.description.awssam.debug.localArguments%", - "type": "array", - "items": { - "type": "string" - } - }, - "skipNewImageCheck": { - "description": "%AWS.configuration.description.awssam.debug.skipNewImageCheck%", - "type": "boolean" - }, - "template": { - "description": "%AWS.configuration.description.awssam.debug.template%", - "properties": { - "parameters": { - "description": "%AWS.configuration.description.awssam.debug.templateParameters%", - "additionalProperties": { - "type": [ - "string", - "number" - ] - }, - "type": "object" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false, - "type": "object" - }, - "api": { - "title": "API Gateway Properties", - "description": "%AWS.configuration.description.awssam.debug.api%", - "properties": { - "path": { - "description": "%AWS.configuration.description.awssam.debug.api.path%", - "type": "string" - }, - "httpMethod": { - "description": "%AWS.configuration.description.awssam.debug.api.httpMethod%", - "type": "string", - "enum": [ - "delete", - "get", - "head", - "options", - "patch", - "post", - "put" - ] - }, - "payload": { - "description": "%AWS.configuration.description.awssam.debug.event%", - "properties": { - "json": { - "description": "%AWS.configuration.description.awssam.debug.event.json%", - "type": "object" - }, - "path": { - "description": "%AWS.configuration.description.awssam.debug.event.path%", - "type": "string" - } - }, - "additionalProperties": false, - "type": "object" - }, - "headers": { - "description": "%AWS.configuration.description.awssam.debug.api.headers%", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "querystring": { - "description": "%AWS.configuration.description.awssam.debug.api.queryString%", - "type": "string" - }, - "stageVariables": { - "description": "%AWS.configuration.description.awssam.debug.api.stageVariables%", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "clientCertificateId": { - "description": "%AWS.configuration.description.awssam.debug.api.clientCertId%", - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "path", - "httpMethod" - ], - "type": "object" - } - }, - "required": [ - "invokeTarget" - ], - "type": "object" - } - }, - "configurationSnippets": [ - { - "label": "%AWS.configuration.description.awssam.debug.snippets.lambdaCode.label%", - "description": "%AWS.configuration.description.awssam.debug.snippets.lambdaCode.description%", - "body": { - "type": "aws-sam", - "request": "direct-invoke", - "name": "${3:Invoke Lambda}", - "invokeTarget": { - "target": "code", - "lambdaHandler": "${1:Function Handler}", - "projectRoot": "^\"\\${workspaceFolder}\"" - }, - "lambda": { - "runtime": "${2:Lambda Runtime}", - "payload": { - "json": {} - } - } - }, - "cloud9": { - "cn": { - "label": "%AWS.configuration.description.awssam.debug.snippets.lambdaCode.label.cn%", - "description": "%AWS.configuration.description.awssam.debug.snippets.lambdaCode.description.cn%" - } - } - }, - { - "label": "%AWS.configuration.description.awssam.debug.snippets.lambdaTemplate.label%", - "description": "%AWS.configuration.description.awssam.debug.snippets.lambdaTemplate.description%", - "body": { - "type": "aws-sam", - "request": "direct-invoke", - "name": "${3:Invoke Lambda}", - "invokeTarget": { - "target": "template", - "templatePath": "${1:Template Location}", - "logicalId": "${2:Function Logical ID}" - }, - "lambda": { - "payload": { - "json": {} - } - } - }, - "cloud9": { - "cn": { - "label": "%AWS.configuration.description.awssam.debug.snippets.lambdaTemplate.label.cn%", - "description": "%AWS.configuration.description.awssam.debug.snippets.lambdaTemplate.description.cn%" - } - } - }, - { - "label": "%AWS.configuration.description.awssam.debug.snippets.api.label%", - "description": "%AWS.configuration.description.awssam.debug.snippets.api.description%", - "body": { - "type": "aws-sam", - "request": "direct-invoke", - "name": "${5:Invoke Lambda with API Gateway}", - "invokeTarget": { - "target": "api", - "templatePath": "${1:Template Location}", - "logicalId": "${2:Function Logical ID}" - }, - "api": { - "path": "${3:Path}", - "httpMethod": "${4:Method}", - "payload": { - "json": {} - } - } - }, - "cloud9": { - "cn": { - "label": "%AWS.configuration.description.awssam.debug.snippets.api.label.cn%", - "description": "%AWS.configuration.description.awssam.debug.snippets.api.description.cn%" - } - } - } - ] - } - ], - "viewsContainers": { - "activitybar": [ - { - "id": "aws-explorer", - "title": "%AWS.title%", - "icon": "resources/aws-logo.svg", - "cloud9": { - "cn": { - "title": "%AWS.title.cn%", - "icon": "resources/aws-cn-logo.svg" - } - } - } - ] - }, - "views": { - "aws-explorer": [ - { - "id": "aws.amazonq.codewhisperer", - "name": "%AWS.amazonq.codewhisperer.title%", - "when": "!isCloud9 && !aws.isSageMaker && !aws.toolkit.amazonq.dismissed && !aws.explorer.showAuthView" - }, - { - "id": "aws.explorer", - "name": "%AWS.lambda.explorerTitle%", - "when": "(isCloud9 || !aws.isWebExtHost) && !aws.explorer.showAuthView" - }, - { - "id": "aws.cdk", - "name": "%AWS.cdk.explorerTitle%", - "when": "!aws.explorer.showAuthView" - }, - { - "id": "aws.codecatalyst", - "name": "%AWS.codecatalyst.explorerTitle%", - "when": "(!isCloud9 && !aws.isSageMaker || isCloud9CodeCatalyst) && !aws.explorer.showAuthView" - }, - { - "type": "webview", - "id": "aws.toolkit.AmazonCommonAuth", - "name": "%AWS.amazonq.login%", - "when": "!isCloud9 && !aws.isSageMaker && aws.explorer.showAuthView" - } - ] - }, - "submenus": [ - { - "id": "aws.toolkit.auth", - "label": "%AWS.submenu.auth.title%", - "icon": "$(ellipsis)", - "when": "isCloud9 || !aws.isWebExtHost" - }, - { - "id": "aws.codecatalyst.submenu", - "label": "%AWS.codecatalyst.submenu.title%", - "icon": "$(ellipsis)", - "when": "isCloud9 || !aws.isWebExtHost" - }, - { - "label": "%AWS.generic.feedback%", - "id": "aws.toolkit.submenu.feedback" - }, - { - "label": "%AWS.generic.help%", - "id": "aws.toolkit.submenu.help" - } - ], - "menus": { - "commandPalette": [ - { - "command": "aws.apig.copyUrl", - "when": "false" - }, - { - "command": "aws.apig.invokeRemoteRestApi", - "when": "false" - }, - { - "command": "aws.deleteCloudFormation", - "when": "false" - }, - { - "command": "aws.downloadStateMachineDefinition", - "when": "false" - }, - { - "command": "aws.ecr.createRepository", - "when": "false" - }, - { - "command": "aws.executeStateMachine", - "when": "false" - }, - { - "command": "aws.copyArn", - "when": "false" - }, - { - "command": "aws.copyName", - "when": "false" - }, - { - "command": "aws.listCommands", - "when": "false" - }, - { - "command": "aws.codecatalyst.listCommands", - "when": "false" - }, - { - "command": "aws.codecatalyst.manageConnections", - "when": "false" - }, - { - "command": "aws.codecatalyst.openDevEnv", - "when": "!isCloud9" - }, - { - "command": "aws.codecatalyst.createDevEnv", - "when": "!isCloud9" - }, - { - "command": "aws.downloadSchemaItemCode", - "when": "false" - }, - { - "command": "aws.deleteLambda", - "when": "false" - }, - { - "command": "aws.downloadLambda", - "when": "false" - }, - { - "command": "aws.invokeLambda", - "when": "false" - }, - { - "command": "aws.copyLambdaUrl", - "when": "false" - }, - { - "command": "aws.viewSchemaItem", - "when": "false" - }, - { - "command": "aws.searchSchema", - "when": "false" - }, - { - "command": "aws.searchSchemaPerRegistry", - "when": "false" - }, - { - "command": "aws.refreshAwsExplorer", - "when": "false" - }, - { - "command": "aws.cdk.refresh", - "when": "false" - }, - { - "command": "aws.cdk.viewDocs", - "when": "false" - }, - { - "command": "aws.ssmDocument.openLocalDocument", - "when": "false" - }, - { - "command": "aws.ssmDocument.openLocalDocumentJson", - "when": "false" - }, - { - "command": "aws.ssmDocument.openLocalDocumentYaml", - "when": "false" - }, - { - "command": "aws.ssmDocument.deleteDocument", - "when": "false" - }, - { - "command": "aws.ssmDocument.updateDocumentVersion", - "when": "false" - }, - { - "command": "aws.copyLogResource", - "when": "resourceScheme == aws-cwl" - }, - { - "command": "aws.saveCurrentLogDataContent", - "when": "resourceScheme == aws-cwl" - }, - { - "command": "aws.s3.editFile", - "when": "resourceScheme == s3-readonly" - }, - { - "command": "aws.cwl.viewLogStream", - "when": "false" - }, - { - "command": "aws.cwl.changeFilterPattern", - "when": "false" - }, - { - "command": "aws.cwl.changeTimeFilter", - "when": "false" - }, - { - "command": "aws.ecr.deleteRepository", - "when": "false" - }, - { - "command": "aws.ecr.copyTagUri", - "when": "false" - }, - { - "command": "aws.ecr.copyRepositoryUri", - "when": "false" - }, - { - "command": "aws.ecr.deleteTag", - "when": "false" - }, - { - "command": "aws.iot.createThing", - "when": "false" - }, - { - "command": "aws.iot.deleteThing", - "when": "false" - }, - { - "command": "aws.iot.createCert", - "when": "false" - }, - { - "command": "aws.iot.deleteCert", - "when": "false" - }, - { - "command": "aws.iot.attachCert", - "when": "false" - }, - { - "command": "aws.iot.attachPolicy", - "when": "false" - }, - { - "command": "aws.iot.activateCert", - "when": "false" - }, - { - "command": "aws.iot.deactivateCert", - "when": "false" - }, - { - "command": "aws.iot.revokeCert", - "when": "false" - }, - { - "command": "aws.iot.createPolicy", - "when": "false" - }, - { - "command": "aws.iot.deletePolicy", - "when": "false" - }, - { - "command": "aws.iot.createPolicyVersion", - "when": "false" - }, - { - "command": "aws.iot.deletePolicyVersion", - "when": "false" - }, - { - "command": "aws.iot.detachCert", - "when": "false" - }, - { - "command": "aws.iot.detachPolicy", - "when": "false" - }, - { - "command": "aws.iot.viewPolicyVersion", - "when": "false" - }, - { - "command": "aws.iot.setDefaultPolicy", - "when": "false" - }, - { - "command": "aws.iot.copyEndpoint", - "when": "false" - }, - { - "command": "aws.deploySamApplication", - "when": "config.aws.samcli.legacyDeploy" - }, - { - "command": "aws.redshift.editConnection", - "when": "false" - }, - { - "command": "aws.redshift.deleteConnection", - "when": "false" - }, - { - "command": "aws.samcli.sync", - "when": "!config.aws.samcli.legacyDeploy" - }, - { - "command": "aws.s3.copyPath", - "when": "false" - }, - { - "command": "aws.s3.createBucket", - "when": "false" - }, - { - "command": "aws.s3.createFolder", - "when": "false" - }, - { - "command": "aws.s3.deleteBucket", - "when": "false" - }, - { - "command": "aws.s3.deleteFile", - "when": "false" - }, - { - "command": "aws.s3.downloadFileAs", - "when": "false" - }, - { - "command": "aws.s3.openFile", - "when": "false" - }, - { - "command": "aws.s3.editFile", - "when": "false" - }, - { - "command": "aws.s3.uploadFileToParent", - "when": "false" - }, - { - "command": "aws.apprunner.startDeployment", - "when": "false" - }, - { - "command": "aws.apprunner.createService", - "when": "false" - }, - { - "command": "aws.apprunner.pauseService", - "when": "false" - }, - { - "command": "aws.apprunner.resumeService", - "when": "false" - }, - { - "command": "aws.apprunner.copyServiceUrl", - "when": "false" - }, - { - "command": "aws.apprunner.open", - "when": "false" - }, - { - "command": "aws.apprunner.deleteService", - "when": "false" - }, - { - "command": "aws.apprunner.createServiceFromEcr", - "when": "false" - }, - { - "command": "aws.resources.copyIdentifier", - "when": "false" - }, - { - "command": "aws.resources.openResourcePreview", - "when": "false" - }, - { - "command": "aws.resources.createResource", - "when": "false" - }, - { - "command": "aws.resources.deleteResource", - "when": "false" - }, - { - "command": "aws.resources.updateResource", - "when": "false" - }, - { - "command": "aws.resources.updateResourceInline", - "when": "false" - }, - { - "command": "aws.resources.saveResource", - "when": "false" - }, - { - "command": "aws.resources.closeResource", - "when": "false" - }, - { - "command": "aws.resources.viewDocs", - "when": "false" - }, - { - "command": "aws.ecs.runCommandInContainer", - "when": "false" - }, - { - "command": "aws.ecs.openTaskInTerminal", - "when": "false" - }, - { - "command": "aws.ecs.enableEcsExec", - "when": "false" - }, - { - "command": "aws.ecs.disableEcsExec", - "when": "false" - }, - { - "command": "aws.ecs.viewDocumentation", - "when": "false" - }, - { - "command": "aws.renderStateMachineGraph", - "when": "false" - }, - { - "command": "aws.toolkit.auth.addConnection", - "when": "false" - }, - { - "command": "aws.toolkit.auth.switchConnections", - "when": "false" - }, - { - "command": "aws.toolkit.auth.help", - "when": "false" - }, - { - "command": "aws.toolkit.auth.manageConnections" - }, - { - "command": "aws.ec2.openRemoteConnection", - "when": "aws.isDevMode" - }, - { - "command": "aws.ec2.openTerminal", - "when": "aws.isDevMode" - }, - { - "command": "aws.ec2.linkToLaunch", - "when": "aws.isDevMode" - }, - { - "command": "aws.ec2.startInstance", - "when": "aws.isDevMode" - }, - { - "command": "aws.ec2.stopInstance", - "when": "aws.isDevMode" - }, - { - "command": "aws.ec2.rebootInstance", - "when": "aws.isDevMode" - }, - { - "command": "aws.dev.openMenu", - "when": "aws.isDevMode || isCloud9" - }, - { - "command": "aws.openInApplicationComposer", - "when": "false" - }, - { - "command": "aws.toolkit.amazonq.learnMore", - "when": "false" - }, - { - "command": "aws.toolkit.amazonq.extensionpage", - "when": "false" - }, - { - "command": "aws.newThreatComposerFile", - "when": "false" - } - ], - "editor/title": [ - { - "command": "aws.previewStateMachine", - "when": "editorLangId == asl || editorLangId == asl-yaml", - "group": "navigation" - }, - { - "command": "aws.saveCurrentLogDataContent", - "when": "resourceScheme == aws-cwl", - "group": "navigation" - }, - { - "command": "aws.cwl.changeFilterPattern", - "when": "resourceScheme == aws-cwl", - "group": "navigation" - }, - { - "command": "aws.cwl.changeTimeFilter", - "when": "resourceScheme == aws-cwl", - "group": "navigation" - }, - { - "command": "aws.s3.editFile", - "when": "resourceScheme == s3-readonly", - "group": "navigation" - }, - { - "command": "aws.ssmDocument.publishDocument", - "when": "editorLangId =~ /^(ssm-yaml|ssm-json)$/", - "group": "navigation" - }, - { - "command": "aws.resources.updateResourceInline", - "when": "resourceScheme == awsResource && !isCloud9 && config.aws.experiments.jsonResourceModification", - "group": "navigation" - }, - { - "command": "aws.resources.closeResource", - "when": "resourcePath =~ /^.+(awsResource.json)$/", - "group": "navigation" - }, - { - "command": "aws.resources.saveResource", - "when": "resourcePath =~ /^.+(awsResource.json)$/", - "group": "navigation" - }, - { - "command": "aws.openInApplicationComposer", - "when": "(editorLangId == json && !(resourceFilename =~ /^.*\\.tc\\.json$/)) || editorLangId == yaml || resourceFilename =~ /^.*\\.(template)$/", - "group": "navigation" - } - ], - "editor/title/context": [ - { - "command": "aws.copyLogResource", - "when": "resourceScheme == aws-cwl", - "group": "1_cutcopypaste@1" - } - ], - "view/title": [ - { - "command": "aws.toolkit.submitFeedback", - "when": "view == aws.explorer && !aws.isWebExtHost", - "group": "navigation@6" - }, - { - "command": "aws.refreshAwsExplorer", - "when": "view == aws.explorer", - "group": "navigation@5" - }, - { - "command": "aws.cdk.refresh", - "when": "view == aws.cdk", - "group": "navigation@1" - }, - { - "command": "aws.toolkit.login", - "when": "view == aws.explorer", - "group": "1_account@1" - }, - { - "command": "aws.showRegion", - "when": "view == aws.explorer", - "group": "1_account@2" - }, - { - "command": "aws.listCommands", - "when": "view == aws.explorer && !isCloud9", - "group": "1_account@3" - }, - { - "command": "aws.lambda.createNewSamApp", - "when": "view == aws.explorer", - "group": "3_lambda@1" - }, - { - "command": "aws.launchConfigForm", - "when": "view == aws.explorer", - "group": "3_lambda@2" - }, - { - "command": "aws.deploySamApplication", - "when": "config.aws.samcli.legacyDeploy && view == aws.explorer", - "group": "3_lambda@3" - }, - { - "command": "aws.samcli.sync", - "when": "!config.aws.samcli.legacyDeploy && view == aws.explorer", - "group": "3_lambda@3" - }, - { - "submenu": "aws.toolkit.submenu.feedback", - "when": "view =~ /^aws\\./ && view != aws.AmazonQChatView && view != aws.amazonq.AmazonCommonAuth", - "group": "y_toolkitMeta@1" - }, - { - "submenu": "aws.toolkit.submenu.help", - "when": "view =~ /^aws\\./ && view != aws.AmazonQChatView && view != aws.amazonq.AmazonCommonAuth", - "group": "y_toolkitMeta@2" - }, - { - "command": "aws.codecatalyst.cloneRepo", - "when": "view == aws.codecatalyst && !isCloud9 && aws.codecatalyst.connected", - "group": "1_codeCatalyst@1" - }, - { - "command": "aws.codecatalyst.createDevEnv", - "when": "view == aws.codecatalyst && !isCloud9 && aws.codecatalyst.connected", - "group": "1_codeCatalyst@1" - }, - { - "command": "aws.codecatalyst.listCommands", - "when": "view == aws.codecatalyst && !isCloud9 && aws.codecatalyst.connected", - "group": "1_codeCatalyst@1" - }, - { - "command": "aws.codecatalyst.openDevEnv", - "when": "view == aws.codecatalyst && !isCloud9 && aws.codecatalyst.connected", - "group": "1_codeCatalyst@1" - }, - { - "command": "aws.codecatalyst.manageConnections", - "when": "view == aws.codecatalyst && !isCloud9 && !aws.codecatalyst.connected", - "group": "2_codeCatalyst@1" - }, - { - "command": "aws.codecatalyst.signout", - "when": "view == aws.codecatalyst && !isCloud9 && aws.codecatalyst.connected", - "group": "2_codeCatalyst@1" - } - ], - "explorer/context": [ - { - "command": "aws.deploySamApplication", - "when": "config.aws.samcli.legacyDeploy && isFileSystemResource && resourceFilename =~ /^template\\.(json|yml|yaml)$/", - "group": "z_aws@1" - }, - { - "command": "aws.samcli.sync", - "when": "!config.aws.samcli.legacyDeploy && isFileSystemResource && resourceFilename =~ /^(template\\.(json|yml|yaml))|(samconfig\\.toml)$/", - "group": "z_aws@1" - }, - { - "command": "aws.uploadLambda", - "when": "explorerResourceIsFolder || isFileSystemResource && resourceFilename =~ /^template\\.(json|yml|yaml)$/", - "group": "z_aws@3" - }, - { - "command": "aws.openInApplicationComposer", - "when": "isFileSystemResource && !(resourceFilename =~ /^.*\\.tc\\.json$/) && resourceFilename =~ /^.*\\.(json|yml|yaml|template)$/", - "group": "z_aws@1" - } - ], - "view/item/context": [ - { - "command": "aws.apig.invokeRemoteRestApi", - "when": "view == aws.explorer && viewItem =~ /^(awsApiGatewayNode)$/", - "group": "0@1" - }, - { - "command": "aws.ec2.openTerminal", - "group": "0@1", - "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/" - }, - { - "command": "aws.ec2.openTerminal", - "group": "inline@1", - "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/" - }, - { - "command": "aws.ec2.linkToLaunch", - "group": "0@1", - "when": "viewItem =~ /^(awsEc2ParentNode)$/" - }, - { - "command": "aws.ec2.linkToLaunch", - "group": "inline@1", - "when": "viewItem =~ /^(awsEc2ParentNode)$/" - }, - { - "command": "aws.ec2.openRemoteConnection", - "group": "0@1", - "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/" - }, - { - "command": "aws.ec2.openRemoteConnection", - "group": "inline@1", - "when": "viewItem =~ /^(awsEc2(Parent|Running)Node)$/" - }, - { - "command": "aws.ec2.startInstance", - "group": "0@1", - "when": "viewItem == awsEc2StoppedNode" - }, - { - "command": "aws.ec2.startInstance", - "group": "inline@1", - "when": "viewItem == awsEc2StoppedNode" - }, - { - "command": "aws.ec2.stopInstance", - "group": "0@1", - "when": "viewItem == awsEc2RunningNode" - }, - { - "command": "aws.ec2.stopInstance", - "group": "inline@1", - "when": "viewItem == awsEc2RunningNode" - }, - { - "command": "aws.ec2.rebootInstance", - "group": "0@1", - "when": "viewItem == awsEc2RunningNode" - }, - { - "command": "aws.ec2.rebootInstance", - "group": "inline@1", - "when": "viewItem == awsEc2RunningNode" - }, - { - "command": "aws.ecr.createRepository", - "when": "view == aws.explorer && viewItem == awsEcrNode", - "group": "inline@1" - }, - { - "command": "aws.iot.createThing", - "when": "view == aws.explorer && viewItem == awsIotThingsNode", - "group": "inline@1" - }, - { - "command": "aws.iot.createCert", - "when": "view == aws.explorer && viewItem == awsIotCertsNode", - "group": "inline@1" - }, - { - "command": "aws.iot.createPolicy", - "when": "view == aws.explorer && viewItem == awsIotPoliciesNode", - "group": "inline@1" - }, - { - "command": "aws.iot.attachCert", - "when": "view == aws.explorer && viewItem == awsIotThingNode", - "group": "inline@1" - }, - { - "command": "aws.iot.attachPolicy", - "when": "view == aws.explorer && viewItem =~ /^awsIotCertificateNode.(Things|Policies)/", - "group": "inline@1" - }, - { - "command": "aws.redshift.editConnection", - "when": "view == aws.explorer && viewItem == awsRedshiftWarehouseNode", - "group": "0@1" - }, - { - "command": "aws.redshift.deleteConnection", - "when": "view == aws.explorer && viewItem == awsRedshiftWarehouseNode", - "group": "0@2" - }, - { - "command": "aws.s3.openFile", - "when": "view == aws.explorer && viewItem == awsS3FileNode && !isCloud9", - "group": "0@1" - }, - { - "command": "aws.s3.editFile", - "when": "view == aws.explorer && viewItem == awsS3FileNode && !isCloud9", - "group": "inline@1" - }, - { - "command": "aws.s3.downloadFileAs", - "when": "view == aws.explorer && viewItem == awsS3FileNode", - "group": "inline@2" - }, - { - "command": "aws.s3.createBucket", - "when": "view == aws.explorer && viewItem == awsS3Node", - "group": "inline@1" - }, - { - "command": "aws.s3.createFolder", - "when": "view == aws.explorer && viewItem =~ /^(awsS3BucketNode|awsS3FolderNode)$/", - "group": "inline@1" - }, - { - "command": "aws.ssmDocument.openLocalDocument", - "when": "view == aws.explorer && viewItem =~ /^(awsDocumentItemNode|awsDocumentItemNodeWriteable)$/", - "group": "inline@1" - }, - { - "command": "aws.s3.uploadFile", - "when": "view == aws.explorer && viewItem =~ /^(awsS3BucketNode|awsS3FolderNode)$/", - "group": "inline@2" - }, - { - "command": "aws.showRegion", - "when": "view == aws.explorer && viewItem == awsRegionNode", - "group": "0@1" - }, - { - "command": "aws.lambda.createNewSamApp", - "when": "view == aws.explorer && viewItem == awsLambdaNode || viewItem == awsRegionNode", - "group": "1@1" - }, - { - "command": "aws.launchConfigForm", - "when": "view == aws.explorer && viewItem == awsLambdaNode || viewItem == awsRegionNode || viewItem == awsCloudFormationRootNode", - "group": "1@1" - }, - { - "command": "aws.deploySamApplication", - "when": "config.aws.samcli.legacyDeploy && view == aws.explorer && viewItem =~ /^(awsLambdaNode|awsRegionNode|awsCloudFormationRootNode)$/", - "group": "1@2" - }, - { - "command": "aws.samcli.sync", - "when": "!config.aws.samcli.legacyDeploy && view == aws.explorer && viewItem =~ /^(awsLambdaNode|awsRegionNode|awsCloudFormationRootNode)$/", - "group": "1@2" - }, - { - "command": "aws.ec2.copyInstanceId", - "when": "view == aws.explorer && viewItem =~ /^(awsEc2(Running|Stopped|Pending)Node)$/", - "group": "2@0" - }, - { - "command": "aws.ecr.copyTagUri", - "when": "view == aws.explorer && viewItem == awsEcrTagNode", - "group": "2@1" - }, - { - "command": "aws.ecr.deleteTag", - "when": "view == aws.explorer && viewItem == awsEcrTagNode", - "group": "3@1" - }, - { - "command": "aws.ecr.copyRepositoryUri", - "when": "view == aws.explorer && viewItem == awsEcrRepositoryNode", - "group": "2@1" - }, - { - "command": "aws.ecr.createRepository", - "when": "view == aws.explorer && viewItem == awsEcrNode", - "group": "0@1" - }, - { - "command": "aws.ecr.deleteRepository", - "when": "view == aws.explorer && viewItem == awsEcrRepositoryNode", - "group": "3@1" - }, - { - "command": "aws.invokeLambda", - "when": "view == aws.explorer && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable|awsCloudFormationFunctionNode)$/", - "group": "0@1" - }, - { - "command": "aws.downloadLambda", - "when": "view == aws.explorer && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable)$/", - "group": "0@2" - }, - { - "command": "aws.uploadLambda", - "when": "view == aws.explorer && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable)$/", - "group": "1@1" - }, - { - "command": "aws.deleteLambda", - "when": "view == aws.explorer && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable)$/", - "group": "4@1" - }, - { - "command": "aws.copyLambdaUrl", - "when": "view == aws.explorer && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable)$/", - "group": "2@0" - }, - { - "command": "aws.deleteCloudFormation", - "when": "view == aws.explorer && viewItem == awsCloudFormationNode", - "group": "3@5" - }, - { - "command": "aws.searchSchema", - "when": "view == aws.explorer && viewItem == awsSchemasNode", - "group": "0@1" - }, - { - "command": "aws.searchSchemaPerRegistry", - "when": "view == aws.explorer && viewItem == awsRegistryItemNode", - "group": "0@1" - }, - { - "command": "aws.viewSchemaItem", - "when": "view == aws.explorer && viewItem == awsSchemaItemNode", - "group": "0@1" - }, - { - "command": "aws.stepfunctions.createStateMachineFromTemplate", - "when": "view == aws.explorer && viewItem == awsStepFunctionsNode", - "group": "0@1" - }, - { - "command": "aws.downloadStateMachineDefinition", - "when": "view == aws.explorer && viewItem == awsStateMachineNode", - "group": "0@1" - }, - { - "command": "aws.renderStateMachineGraph", - "when": "view == aws.explorer && viewItem == awsStateMachineNode", - "group": "0@2" - }, - { - "command": "aws.cdk.renderStateMachineGraph", - "when": "viewItem == awsCdkStateMachineNode", - "group": "inline@1" - }, - { - "command": "aws.cdk.renderStateMachineGraph", - "when": "viewItem == awsCdkStateMachineNode", - "group": "0@1" - }, - { - "command": "aws.executeStateMachine", - "when": "view == aws.explorer && viewItem == awsStateMachineNode", - "group": "0@3" - }, - { - "command": "aws.iot.createThing", - "when": "view == aws.explorer && viewItem == awsIotThingsNode", - "group": "0@1" - }, - { - "command": "aws.iot.createCert", - "when": "view == aws.explorer && viewItem == awsIotCertsNode", - "group": "0@1" - }, - { - "command": "aws.iot.createPolicy", - "when": "view == aws.explorer && viewItem == awsIotPoliciesNode", - "group": "0@1" - }, - { - "command": "aws.iot.createPolicyVersion", - "when": "view == aws.explorer && viewItem == awsIotPolicyNode.WithVersions", - "group": "0@1" - }, - { - "command": "aws.iot.viewPolicyVersion", - "when": "view == aws.explorer && viewItem =~ /^awsIotPolicyVersionNode./", - "group": "0@1" - }, - { - "command": "aws.iot.attachCert", - "when": "view == aws.explorer && viewItem == awsIotThingNode", - "group": "0@1" - }, - { - "command": "aws.iot.attachPolicy", - "when": "view == aws.explorer && viewItem =~ /^awsIotCertificateNode.(Things|Policies)/", - "group": "0@1" - }, - { - "command": "aws.s3.createBucket", - "when": "view == aws.explorer && viewItem == awsS3Node", - "group": "0@1" - }, - { - "command": "aws.s3.downloadFileAs", - "when": "view == aws.explorer && viewItem == awsS3FileNode", - "group": "0@1" - }, - { - "command": "aws.s3.uploadFile", - "when": "view == aws.explorer && viewItem =~ /^(awsS3BucketNode|awsS3FolderNode)$/", - "group": "0@1" - }, - { - "command": "aws.s3.uploadFileToParent", - "when": "view == aws.explorer && viewItem == awsS3FileNode", - "group": "1@1" - }, - { - "command": "aws.s3.createFolder", - "when": "view == aws.explorer && viewItem =~ /^(awsS3BucketNode|awsS3FolderNode)$/", - "group": "1@1" - }, - { - "command": "aws.iot.deactivateCert", - "when": "view == aws.explorer && viewItem =~ /^awsIotCertificateNode.(Things|Policies).ACTIVE$/", - "group": "1@1" - }, - { - "command": "aws.iot.activateCert", - "when": "view == aws.explorer && viewItem =~ /^awsIotCertificateNode.(Things|Policies).INACTIVE$/", - "group": "1@1" - }, - { - "command": "aws.iot.revokeCert", - "when": "view == aws.explorer && viewItem =~ /^awsIotCertificateNode.(Things|Policies).(ACTIVE|INACTIVE)$/", - "group": "1@2" - }, - { - "command": "aws.iot.setDefaultPolicy", - "when": "view == aws.explorer && viewItem == awsIotPolicyVersionNode.NONDEFAULT", - "group": "1@1" - }, - { - "command": "aws.iot.copyEndpoint", - "when": "view == aws.explorer && viewItem == awsIotNode", - "group": "2@1" - }, - { - "command": "aws.copyName", - "when": "view == aws.explorer && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable|awsCloudFormationFunctionNode|awsStateMachineNode|awsCloudFormationNode|awsS3BucketNode|awsS3FolderNode|awsS3FileNode|awsApiGatewayNode|awsIotThingNode)$|^(awsAppRunnerServiceNode|awsIotCertificateNode|awsIotPolicyNode|awsIotPolicyVersionNode|(awsEc2(Running|Pending|Stopped)Node))/", - "group": "2@1" - }, - { - "command": "aws.copyArn", - "when": "view == aws.explorer && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable|awsCloudFormationFunctionNode|awsStateMachineNode|awsCloudFormationNode|awsCloudWatchLogNode|awsS3BucketNode|awsS3FolderNode|awsS3FileNode|awsApiGatewayNode|awsEcrRepositoryNode|awsIotThingNode)$|^(awsAppRunnerServiceNode|awsEcsServiceNode|awsIotCertificateNode|awsIotPolicyNode|awsIotPolicyVersionNode|awsMdeInstanceNode|(awsEc2(Running|Pending|Stopped)Node))/", - "group": "2@2" - }, - { - "command": "aws.cwl.searchLogGroup", - "group": "0@1", - "when": "view == aws.explorer && viewItem =~ /^awsCloudWatchLogNode|awsCloudWatchLogParentNode$/" - }, - { - "command": "aws.cwl.searchLogGroup", - "group": "inline@1", - "when": "view == aws.explorer && viewItem =~ /^awsCloudWatchLogNode|awsCloudWatchLogParentNode$/" - }, - { - "command": "aws.apig.copyUrl", - "when": "view == aws.explorer && viewItem =~ /^(awsApiGatewayNode)$/", - "group": "2@0" - }, - { - "command": "aws.s3.copyPath", - "when": "view == aws.explorer && viewItem =~ /^(awsS3FolderNode|awsS3FileNode)$/", - "group": "2@3" - }, - { - "command": "aws.s3.presignedURL", - "when": "view == aws.explorer && viewItem =~ /^(awsS3FileNode)$/", - "group": "2@4" - }, - { - "command": "aws.iot.detachCert", - "when": "view == aws.explorer && viewItem =~ /^(awsIotCertificateNode.Things)/", - "group": "3@1" - }, - { - "command": "aws.iot.detachPolicy", - "when": "view == aws.explorer && viewItem == awsIotPolicyNode.Certificates", - "group": "3@1" - }, - { - "command": "aws.iot.deleteThing", - "when": "view == aws.explorer && viewItem == awsIotThingNode", - "group": "3@1" - }, - { - "command": "aws.iot.deleteCert", - "when": "view == aws.explorer && viewItem =~ /^awsIotCertificateNode.Policies/", - "group": "3@1" - }, - { - "command": "aws.iot.deletePolicy", - "when": "view == aws.explorer && viewItem == awsIotPolicyNode.WithVersions", - "group": "3@1" - }, - { - "command": "aws.iot.deletePolicyVersion", - "when": "view == aws.explorer && viewItem == awsIotPolicyVersionNode.NONDEFAULT", - "group": "3@1" - }, - { - "command": "aws.s3.deleteBucket", - "when": "view == aws.explorer && viewItem == awsS3BucketNode", - "group": "3@1" - }, - { - "command": "aws.s3.deleteFile", - "when": "view == aws.explorer && viewItem == awsS3FileNode", - "group": "3@1" - }, - { - "command": "aws.downloadSchemaItemCode", - "when": "view == aws.explorer && viewItem == awsSchemaItemNode", - "group": "1@1" - }, - { - "command": "aws.cwl.viewLogStream", - "group": "0@1", - "when": "view == aws.explorer && viewItem == awsCloudWatchLogNode" - }, - { - "command": "aws.ssmDocument.openLocalDocumentYaml", - "group": "0@1", - "when": "view == aws.explorer && viewItem =~ /^(awsDocumentItemNode|awsDocumentItemNodeWriteable)$/" - }, - { - "command": "aws.ssmDocument.openLocalDocumentJson", - "group": "0@2", - "when": "view == aws.explorer && viewItem =~ /^(awsDocumentItemNode|awsDocumentItemNodeWriteable)$/" - }, - { - "command": "aws.ssmDocument.updateDocumentVersion", - "group": "2@1", - "when": "view == aws.explorer && viewItem == awsDocumentItemNodeWriteable" - }, - { - "command": "aws.ssmDocument.deleteDocument", - "group": "3@2", - "when": "view == aws.explorer && viewItem == awsDocumentItemNodeWriteable" - }, - { - "command": "aws.ecs.runCommandInContainer", - "group": "0@1", - "when": "view == aws.explorer && viewItem =~ /^(awsEcsContainerNodeExec)(.*)$/" - }, - { - "command": "aws.ecs.openTaskInTerminal", - "group": "0@2", - "when": "view == aws.explorer && viewItem =~ /^(awsEcsContainerNodeExec)(.*)$/ && !isCloud9" - }, - { - "command": "aws.ecs.enableEcsExec", - "group": "0@2", - "when": "view == aws.explorer && viewItem == awsEcsServiceNode.DISABLED" - }, - { - "command": "aws.ecs.disableEcsExec", - "group": "0@2", - "when": "view == aws.explorer && viewItem == awsEcsServiceNode.ENABLED" - }, - { - "command": "aws.ecs.viewDocumentation", - "group": "1@3", - "when": "view == aws.explorer && viewItem =~ /^(awsEcsClusterNode|awsEcsContainerNode)$|^awsEcsServiceNode/" - }, - { - "command": "aws.resources.configure", - "when": "view == aws.explorer && viewItem == resourcesRootNode", - "group": "1@1" - }, - { - "command": "aws.resources.configure", - "when": "view == aws.explorer && viewItem == resourcesRootNode", - "group": "inline@1" - }, - { - "command": "aws.resources.openResourcePreview", - "when": "view == aws.explorer && viewItem =~ /^(.*)(ResourceNode)$/", - "group": "1@1" - }, - { - "command": "aws.resources.copyIdentifier", - "when": "view == aws.explorer && viewItem =~ /^(.*)(ResourceNode)$/", - "group": "1@1" - }, - { - "command": "aws.resources.viewDocs", - "when": "view == aws.explorer && viewItem =~ /^(.*)(Documented)(.*)(ResourceTypeNode)$/", - "group": "1@1" - }, - { - "command": "aws.resources.createResource", - "when": "view == aws.explorer && viewItem =~ /^(.*)(Creatable)(.*)(ResourceTypeNode)$/ && !isCloud9 && config.aws.experiments.jsonResourceModification", - "group": "2@1" - }, - { - "command": "aws.resources.createResource", - "when": "view == aws.explorer && viewItem =~ /^(.*)(Creatable)(.*)(ResourceTypeNode)$/ && !isCloud9 && config.aws.experiments.jsonResourceModification", - "group": "inline@1" - }, - { - "command": "aws.resources.updateResource", - "when": "view == aws.explorer && viewItem =~ /^(.*)(Updatable)(.*)(ResourceNode)$/ && !isCloud9 && config.aws.experiments.jsonResourceModification", - "group": "2@1" - }, - { - "command": "aws.resources.deleteResource", - "when": "view == aws.explorer && viewItem =~ /^(.*)(Deletable)(.*)(ResourceNode)$/ && !isCloud9 && config.aws.experiments.jsonResourceModification", - "group": "2@2" - }, - { - "command": "aws.apprunner.createServiceFromEcr", - "group": "0@2", - "when": "view == aws.explorer && viewItem =~ /awsEcrTagNode|awsEcrRepositoryNode/" - }, - { - "command": "aws.apprunner.startDeployment", - "group": "0@1", - "when": "view == aws.explorer && viewItem == awsAppRunnerServiceNode.RUNNING" - }, - { - "command": "aws.apprunner.createService", - "group": "0@2", - "when": "view == aws.explorer && viewItem == awsAppRunnerNode" - }, - { - "command": "aws.apprunner.pauseService", - "group": "0@3", - "when": "view == aws.explorer && viewItem == awsAppRunnerServiceNode.RUNNING" - }, - { - "command": "aws.apprunner.resumeService", - "group": "0@3", - "when": "view == aws.explorer && viewItem == awsAppRunnerServiceNode.PAUSED" - }, - { - "command": "aws.apprunner.copyServiceUrl", - "group": "1@1", - "when": "view == aws.explorer && viewItem == awsAppRunnerServiceNode.RUNNING" - }, - { - "command": "aws.apprunner.open", - "group": "1@2", - "when": "view == aws.explorer && viewItem == awsAppRunnerServiceNode.RUNNING" - }, - { - "command": "aws.apprunner.deleteService", - "group": "3@1", - "when": "view == aws.explorer && viewItem =~ /awsAppRunnerServiceNode.[RUNNING|PAUSED|CREATE_FAILED]/" - }, - { - "command": "aws.cloudFormation.newTemplate", - "group": "0@1", - "when": "view == aws.explorer && viewItem == awsCloudFormationRootNode" - }, - { - "command": "aws.sam.newTemplate", - "group": "0@2", - "when": "view == aws.explorer && viewItem == awsCloudFormationRootNode" - }, - { - "command": "aws.cdk.viewDocs", - "when": "viewItem == awsCdkRootNode", - "group": "0@2" - }, - { - "command": "aws.toolkit.auth.addConnection", - "when": "viewItem == awsAuthNode", - "group": "0@1" - }, - { - "command": "aws.toolkit.auth.switchConnections", - "when": "viewItem == awsAuthNode", - "group": "0@2" - }, - { - "command": "aws.toolkit.auth.signout", - "when": "viewItem == awsAuthNode && !isCloud9", - "group": "0@3" - }, - { - "command": "aws.toolkit.auth.help", - "when": "viewItem == awsAuthNode", - "group": "inline@1" - }, - { - "submenu": "aws.toolkit.auth", - "when": "viewItem == awsAuthNode", - "group": "inline@2" - }, - { - "submenu": "aws.codecatalyst.submenu", - "when": "viewItem =~ /^awsCodeCatalystNode/", - "group": "inline@1" - }, - { - "command": "aws.codecatalyst.manageConnections", - "when": "viewItem =~ /^awsCodeCatalystNode/", - "group": "0@1" - }, - { - "command": "aws.codecatalyst.signout", - "when": "viewItem =~ /^awsCodeCatalystNode/&& !isCloud9 && aws.codecatalyst.connected", - "group": "0@2" - } - ], - "aws.toolkit.auth": [ - { - "command": "aws.toolkit.auth.manageConnections", - "group": "0@1" - }, - { - "command": "aws.toolkit.auth.switchConnections", - "group": "0@2" - }, - { - "command": "aws.toolkit.auth.signout", - "enablement": "!isCloud9", - "group": "0@3" - } - ], - "aws.toolkit.submenu.feedback": [ - { - "command": "aws.toolkit.submitFeedback", - "when": "!aws.isWebExtHost", - "group": "1_feedback@1" - }, - { - "command": "aws.toolkit.createIssueOnGitHub", - "group": "1_feedback@2" - } - ], - "aws.toolkit.submenu.help": [ - { - "command": "aws.quickStart", - "when": "isCloud9", - "group": "1_help@1" - }, - { - "command": "aws.toolkit.help", - "group": "1_help@2" - }, - { - "command": "aws.toolkit.github", - "group": "1_help@3" - }, - { - "command": "aws.toolkit.aboutExtension", - "group": "1_help@4" - }, - { - "command": "aws.toolkit.viewLogs", - "group": "1_help@5" - } - ], - "file/newFile": [ - { - "command": "aws.newThreatComposerFile" - } - ] - }, - "commands": [ - { - "command": "aws.accessanalyzer.iamPolicyChecks", - "title": "%AWS.command.accessanalyzer.iamPolicyChecks%", - "category": "%AWS.title%" - }, - { - "command": "aws.launchConfigForm", - "title": "%AWS.command.launchConfigForm.title%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apig.copyUrl", - "title": "%AWS.command.apig.copyUrl%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apig.invokeRemoteRestApi", - "title": "%AWS.command.apig.invokeRemoteRestApi%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%", - "title": "%AWS.command.apig.invokeRemoteRestApi.cn%" - } - } - }, - { - "command": "aws.lambda.createNewSamApp", - "title": "%AWS.command.createNewSamApp%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.toolkit.login", - "title": "%AWS.command.login%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "title": "%AWS.command.login.cn%", - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.toolkit.credentials.profile.create", - "title": "%AWS.command.credentials.profile.create%", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.toolkit.credentials.edit", - "title": "%AWS.command.credentials.edit%", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.codecatalyst.openOrg", - "title": "%AWS.command.codecatalyst.openOrg%", - "category": "AWS", - "enablement": "isCloud9 || !aws.isWebExtHost" - }, - { - "command": "aws.codecatalyst.openProject", - "title": "%AWS.command.codecatalyst.openProject%", - "category": "AWS", - "enablement": "isCloud9 || !aws.isWebExtHost" - }, - { - "command": "aws.codecatalyst.openRepo", - "title": "%AWS.command.codecatalyst.openRepo%", - "category": "AWS", - "enablement": "isCloud9 || !aws.isWebExtHost" - }, - { - "command": "aws.codecatalyst.openDevEnv", - "title": "%AWS.command.codecatalyst.openDevEnv%", - "category": "AWS", - "enablement": "!isCloud9 && !aws.isWebExtHost" - }, - { - "command": "aws.codecatalyst.listCommands", - "title": "%AWS.command.codecatalyst.listCommands%", - "category": "AWS", - "enablement": "!isCloud9 && !aws.isWebExtHost" - }, - { - "command": "aws.codecatalyst.cloneRepo", - "title": "%AWS.command.codecatalyst.cloneRepo%", - "category": "AWS", - "enablement": "!isCloud9 && !aws.isWebExtHost" - }, - { - "command": "aws.codecatalyst.createDevEnv", - "title": "%AWS.command.codecatalyst.createDevEnv%", - "category": "AWS", - "enablement": "!isCloud9 && !aws.isWebExtHost" - }, - { - "command": "aws.codecatalyst.signout", - "title": "%AWS.command.codecatalyst.signout%", - "category": "AWS", - "icon": "$(debug-disconnect)", - "enablement": "isCloud9 || !aws.isWebExtHost" - }, - { - "command": "aws.toolkit.auth.addConnection", - "title": "%AWS.command.auth.addConnection%", - "category": "%AWS.title%" - }, - { - "command": "aws.toolkit.auth.manageConnections", - "title": "%AWS.command.auth.showConnectionsPage%", - "category": "%AWS.title%" - }, - { - "command": "aws.codecatalyst.manageConnections", - "title": "%AWS.command.auth.showConnectionsPage%", - "category": "%AWS.title%" - }, - { - "command": "aws.toolkit.auth.switchConnections", - "title": "%AWS.command.auth.switchConnections%", - "category": "%AWS.title%" - }, - { - "command": "aws.toolkit.auth.signout", - "title": "%AWS.command.auth.signout%", - "category": "%AWS.title%", - "enablement": "!isCloud9" - }, - { - "command": "aws.toolkit.auth.help", - "title": "%AWS.generic.viewDocs%", - "category": "%AWS.title%", - "icon": "$(question)" - }, - { - "command": "aws.toolkit.createIssueOnGitHub", - "title": "%AWS.command.createIssueOnGitHub%", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ec2.openTerminal", - "title": "%AWS.command.ec2.openTerminal%", - "icon": "$(terminal-view-icon)", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ec2.linkToLaunch", - "title": "%AWS.command.ec2.linkToLaunch%", - "icon": "$(add)", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ec2.openRemoteConnection", - "title": "%AWS.command.ec2.openRemoteConnection%", - "icon": "$(remote-explorer)", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ec2.startInstance", - "title": "%AWS.command.ec2.startInstance%", - "icon": "$(debug-start)", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ec2.stopInstance", - "title": "%AWS.command.ec2.stopInstance%", - "icon": "$(debug-stop)", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ec2.rebootInstance", - "title": "%AWS.command.ec2.rebootInstance%", - "icon": "$(debug-restart)", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ec2.copyInstanceId", - "title": "%AWS.command.ec2.copyInstanceId%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecr.copyTagUri", - "title": "%AWS.command.ecr.copyTagUri%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecr.deleteTag", - "title": "%AWS.command.ecr.deleteTag%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecr.copyRepositoryUri", - "title": "%AWS.command.ecr.copyRepositoryUri%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecr.createRepository", - "title": "%AWS.command.ecr.createRepository%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(add)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecr.deleteRepository", - "title": "%AWS.command.ecr.deleteRepository%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.showRegion", - "title": "%AWS.command.showRegion%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.createThing", - "title": "%AWS.command.iot.createThing%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(add)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.deleteThing", - "title": "%AWS.generic.promptDelete%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.createCert", - "title": "%AWS.command.iot.createCert%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(add)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.deleteCert", - "title": "%AWS.generic.promptDelete%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.attachCert", - "title": "%AWS.command.iot.attachCert%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(aws-generic-attach-file)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.attachPolicy", - "title": "%AWS.command.iot.attachPolicy%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(aws-generic-attach-file)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.activateCert", - "title": "%AWS.command.iot.activateCert%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.deactivateCert", - "title": "%AWS.command.iot.deactivateCert%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.revokeCert", - "title": "%AWS.command.iot.revokeCert%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.createPolicy", - "title": "%AWS.command.iot.createPolicy%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(add)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.deletePolicy", - "title": "%AWS.generic.promptDelete%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.createPolicyVersion", - "title": "%AWS.command.iot.createPolicyVersion%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.deletePolicyVersion", - "title": "%AWS.generic.promptDelete%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.detachCert", - "title": "%AWS.command.iot.detachCert%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.detachPolicy", - "title": "%AWS.command.iot.detachCert%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.viewPolicyVersion", - "title": "%AWS.command.iot.viewPolicyVersion%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.setDefaultPolicy", - "title": "%AWS.command.iot.setDefaultPolicy%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.iot.copyEndpoint", - "title": "%AWS.command.iot.copyEndpoint%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.redshift.editConnection", - "title": "Edit connection", - "category": "%AWS.title%" - }, - { - "command": "aws.redshift.deleteConnection", - "title": "Delete connection", - "category": "%AWS.title%" - }, - { - "command": "aws.s3.presignedURL", - "title": "%AWS.command.s3.presignedURL%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost" - }, - { - "command": "aws.s3.copyPath", - "title": "%AWS.command.s3.copyPath%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.s3.downloadFileAs", - "title": "%AWS.command.s3.downloadFileAs%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(cloud-download)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.s3.openFile", - "title": "%AWS.command.s3.openFile%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(open-preview)" - }, - { - "command": "aws.s3.editFile", - "title": "%AWS.command.s3.editFile%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(edit)" - }, - { - "command": "aws.s3.uploadFile", - "title": "%AWS.command.s3.uploadFile%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(cloud-upload)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.s3.uploadFileToParent", - "title": "%AWS.command.s3.uploadFileToParent%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.s3.createFolder", - "title": "%AWS.command.s3.createFolder%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(new-folder)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.s3.createBucket", - "title": "%AWS.command.s3.createBucket%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(aws-s3-create-bucket)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.s3.deleteBucket", - "title": "%AWS.generic.promptDelete%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.s3.deleteFile", - "title": "%AWS.generic.promptDelete%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.invokeLambda", - "title": "%AWS.command.invokeLambda%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "title": "%AWS.command.invokeLambda.cn%", - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.downloadLambda", - "title": "%AWS.command.downloadLambda%", - "category": "%AWS.title%", - "enablement": "viewItem == awsRegionFunctionNodeDownloadable", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.uploadLambda", - "title": "%AWS.command.uploadLambda%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.deleteLambda", - "title": "%AWS.generic.promptDelete%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.copyLambdaUrl", - "title": "%AWS.generic.copyUrl%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.deploySamApplication", - "title": "%AWS.command.deploySamApplication%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.toolkit.submitFeedback", - "title": "%AWS.command.submitFeedback%", - "enablement": "!aws.isWebExtHost", - "category": "%AWS.title%", - "icon": "$(comment)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.refreshAwsExplorer", - "title": "%AWS.command.refreshAwsExplorer%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "icon": { - "dark": "resources/icons/vscode/dark/refresh.svg", - "light": "resources/icons/vscode/light/refresh.svg" - } - }, - { - "command": "aws.samcli.detect", - "title": "%AWS.command.samcli.detect%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.deleteCloudFormation", - "title": "%AWS.command.deleteCloudFormation%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.downloadStateMachineDefinition", - "title": "%AWS.command.downloadStateMachineDefinition%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.executeStateMachine", - "title": "%AWS.command.executeStateMachine%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.renderStateMachineGraph", - "title": "%AWS.command.renderStateMachineGraph%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.copyArn", - "title": "%AWS.command.copyArn%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.copyName", - "title": "%AWS.command.copyName%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.listCommands", - "title": "%AWS.command.listCommands%", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "title": "%AWS.command.listCommands.cn%", - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.viewSchemaItem", - "title": "%AWS.command.viewSchemaItem%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.searchSchema", - "title": "%AWS.command.searchSchema%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.searchSchemaPerRegistry", - "title": "%AWS.command.searchSchemaPerRegistry%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.downloadSchemaItemCode", - "title": "%AWS.command.downloadSchemaItemCode%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.toolkit.viewLogs", - "title": "%AWS.command.viewLogs%", - "category": "%AWS.title%" - }, - { - "command": "aws.toolkit.help", - "title": "%AWS.command.help%", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.toolkit.github", - "title": "%AWS.command.github%", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.quickStart", - "title": "%AWS.command.quickStart%", - "category": "%AWS.title%", - "enablement": "isCloud9", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.cdk.refresh", - "title": "%AWS.command.refreshCdkExplorer%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": { - "dark": "resources/icons/vscode/dark/refresh.svg", - "light": "resources/icons/vscode/light/refresh.svg" - }, - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.cdk.viewDocs", - "title": "%AWS.generic.viewDocs%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost" - }, - { - "command": "aws.stepfunctions.createStateMachineFromTemplate", - "title": "%AWS.command.stepFunctions.createStateMachineFromTemplate%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.stepfunctions.publishStateMachine", - "title": "%AWS.command.stepFunctions.publishStateMachine%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.previewStateMachine", - "title": "%AWS.command.stepFunctions.previewStateMachine%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(aws-stepfunctions-preview)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.cdk.renderStateMachineGraph", - "title": "%AWS.command.cdk.previewStateMachine%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "AWS", - "icon": "$(aws-stepfunctions-preview)" - }, - { - "command": "aws.toolkit.aboutExtension", - "title": "%AWS.command.aboutToolkit%", - "category": "%AWS.title%" - }, - { - "command": "aws.cwl.viewLogStream", - "title": "%AWS.command.viewLogStream%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ssmDocument.createLocalDocument", - "title": "%AWS.command.ssmDocument.createLocalDocument%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ssmDocument.openLocalDocument", - "title": "%AWS.command.ssmDocument.openLocalDocument%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(cloud-download)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ssmDocument.openLocalDocumentJson", - "title": "%AWS.command.ssmDocument.openLocalDocumentJson%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ssmDocument.openLocalDocumentYaml", - "title": "%AWS.command.ssmDocument.openLocalDocumentYaml%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ssmDocument.deleteDocument", - "title": "%AWS.command.ssmDocument.deleteDocument%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ssmDocument.publishDocument", - "title": "%AWS.command.ssmDocument.publishDocument%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(cloud-upload)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ssmDocument.updateDocumentVersion", - "title": "%AWS.command.ssmDocument.updateDocumentVersion%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.copyLogResource", - "title": "%AWS.command.copyLogResource%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(files)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.cwl.searchLogGroup", - "title": "%AWS.command.cloudWatchLogs.searchLogGroup%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(search-view-icon)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.saveCurrentLogDataContent", - "title": "%AWS.command.saveCurrentLogDataContent%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(save)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.cwl.changeFilterPattern", - "title": "%AWS.command.cwl.changeFilterPattern%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(search-view-icon)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.cwl.changeTimeFilter", - "title": "%AWS.command.cwl.changeTimeFilter%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(calendar)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.addSamDebugConfig", - "title": "%AWS.command.addSamDebugConfig%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.toggleSamCodeLenses", - "title": "%AWS.command.toggleSamCodeLenses%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecs.runCommandInContainer", - "title": "%AWS.ecs.runCommandInContainer%", - "category": "%AWS.title%", - "enablement": "viewItem == awsEcsContainerNodeExecEnabled", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecs.openTaskInTerminal", - "title": "%AWS.ecs.openTaskInTerminal%", - "category": "%AWS.title%", - "enablement": "viewItem == awsEcsContainerNodeExecEnabled", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecs.enableEcsExec", - "title": "%AWS.ecs.enableEcsExec%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecs.viewDocumentation", - "title": "%AWS.generic.viewDocs%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.copyIdentifier", - "title": "%AWS.command.resources.copyIdentifier%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.openResourcePreview", - "title": "%AWS.generic.preview%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(open-preview)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.createResource", - "title": "%AWS.generic.create%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(add)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.deleteResource", - "title": "%AWS.generic.promptDelete%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.updateResource", - "title": "%AWS.generic.promptUpdate%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(pencil)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.updateResourceInline", - "title": "%AWS.generic.promptUpdate%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(pencil)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.saveResource", - "title": "%AWS.generic.save%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(save)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.closeResource", - "title": "%AWS.generic.close%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(close)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.viewDocs", - "title": "%AWS.generic.viewDocs%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(book)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.resources.configure", - "title": "%AWS.command.resources.configure%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "icon": "$(gear)", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apprunner.createService", - "title": "%AWS.command.apprunner.createService%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.ecs.disableEcsExec", - "title": "%AWS.ecs.disableEcsExec%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apprunner.createServiceFromEcr", - "title": "%AWS.command.apprunner.createServiceFromEcr%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apprunner.pauseService", - "title": "%AWS.command.apprunner.pauseService%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apprunner.resumeService", - "title": "%AWS.command.apprunner.resumeService%", - "category": "AWS", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apprunner.copyServiceUrl", - "title": "%AWS.command.apprunner.copyServiceUrl%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apprunner.open", - "title": "%AWS.command.apprunner.open%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apprunner.deleteService", - "title": "%AWS.generic.promptDelete%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.apprunner.startDeployment", - "title": "%AWS.command.apprunner.startDeployment%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.cloudFormation.newTemplate", - "title": "%AWS.command.cloudFormation.newTemplate%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.sam.newTemplate", - "title": "%AWS.command.sam.newTemplate%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } - } - }, - { - "command": "aws.samcli.sync", - "title": "%AWS.command.samcli.sync%", - "category": "%AWS.title%", - "enablement": "isCloud9 || !aws.isWebExtHost" - }, - { - "command": "aws.toolkit.amazonq.learnMore", - "title": "%AWS.amazonq.learnMore%", - "category": "%AWS.title%" - }, - { - "command": "aws.toolkit.amazonq.extensionpage", - "title": "Open Amazon Q Extension", - "category": "%AWS.title%" - }, - { - "command": "aws.dev.openMenu", - "title": "Open Developer Menu", - "category": "AWS (Developer)", - "enablement": "aws.isDevMode" - }, - { - "command": "aws.dev.viewLogs", - "title": "Watch Logs", - "category": "AWS (Developer)" - }, - { - "command": "aws.openInApplicationComposerDialog", - "title": "%AWS.command.applicationComposer.openDialog%", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } + "icons": { + "aws-amazonq-q-gradient": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1aa" } }, - { - "command": "aws.openInApplicationComposer", - "title": "%AWS.command.applicationComposer.open%", - "category": "%AWS.title%", - "icon": { - "dark": "resources/icons/aws/applicationcomposer/icon-dark.svg", - "light": "resources/icons/aws/applicationcomposer/icon.svg" - }, - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } + "aws-amazonq-q-squid-ink": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1ab" } }, - { - "command": "aws.createNewThreatComposer", - "title": "%AWS.command.threatComposer.createNew%", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } + "aws-amazonq-q-white": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1ac" } }, - { - "command": "aws.newThreatComposerFile", - "title": "%AWS.command.threatComposer.newFile%", - "category": "%AWS.title%", - "cloud9": { - "cn": { - "category": "%AWS.title.cn%" - } + "aws-amazonq-severity-critical": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1ad" } - } - ], - "jsonValidation": [ - { - "fileMatch": ".aws/templates.json", - "url": "./dist/src/templates/templates.json" - }, - { - "fileMatch": "*ecs-task-def.json", - "url": "https://ecs-intellisense.s3-us-west-2.amazonaws.com/task-definition/schema.json" - } - ], - "languages": [ - { - "id": "asl", - "extensions": [ - ".asl.json", - ".asl" - ], - "aliases": [ - "Amazon States Language" - ] - }, - { - "id": "asl-yaml", - "aliases": [ - "Amazon States Language (YAML)" - ], - "extensions": [ - ".asl.yaml", - ".asl.yml" - ] - }, - { - "id": "ssm-json", - "extensions": [ - ".ssm.json" - ], - "aliases": [ - "AWS Systems Manager Document (JSON)" - ] - }, - { - "id": "ssm-yaml", - "extensions": [ - ".ssm.yaml", - ".ssm.yml" - ], - "aliases": [ - "AWS Systems Manager Document (YAML)" - ] - } - ], - "keybindings": [ - { - "command": "aws.previewStateMachine", - "key": "ctrl+shift+v", - "mac": "cmd+shift+v", - "when": "editorTextFocus && editorLangId == asl || editorTextFocus && editorLangId == asl-yaml" - } - ], - "grammars": [ - { - "language": "asl", - "scopeName": "source.asl", - "path": "./syntaxes/ASL.tmLanguage" - }, - { - "language": "asl-yaml", - "scopeName": "source.asl.yaml", - "path": "./syntaxes/asl-yaml.tmLanguage.json" }, - { - "language": "ssm-json", - "scopeName": "source.ssmjson", - "path": "./syntaxes/SSMJSON.tmLanguage" - }, - { - "language": "ssm-yaml", - "scopeName": "source.ssmyaml", - "path": "./syntaxes/SSMYAML.tmLanguage" - } - ], - "resourceLabelFormatters": [ - { - "scheme": "aws-cwl", - "formatting": { - "label": "${path}", - "separator": "/" + "aws-amazonq-severity-high": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1ae" } }, - { - "scheme": "s3*", - "formatting": { - "label": "[S3] ${path}", - "separator": "/" - } - } - ], - "walkthroughs": [], - "icons": { - "aws-amazonq-q-gradient": { + "aws-amazonq-severity-info": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1aa" + "fontCharacter": "\\f1af" } }, - "aws-amazonq-q-squid-ink": { + "aws-amazonq-severity-low": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ab" + "fontCharacter": "\\f1b0" } }, - "aws-amazonq-q-white": { + "aws-amazonq-severity-medium": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ac" + "fontCharacter": "\\f1b1" } }, "aws-amazonq-transform-arrow-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ad" + "fontCharacter": "\\f1b2" } }, "aws-amazonq-transform-arrow-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ae" + "fontCharacter": "\\f1b3" } }, "aws-amazonq-transform-default-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1af" + "fontCharacter": "\\f1b4" } }, "aws-amazonq-transform-default-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b0" + "fontCharacter": "\\f1b5" } }, "aws-amazonq-transform-dependencies-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b1" + "fontCharacter": "\\f1b6" } }, "aws-amazonq-transform-dependencies-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b2" + "fontCharacter": "\\f1b7" } }, "aws-amazonq-transform-file-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b3" + "fontCharacter": "\\f1b8" } }, "aws-amazonq-transform-file-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b4" + "fontCharacter": "\\f1b9" } }, "aws-amazonq-transform-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b5" + "fontCharacter": "\\f1ba" } }, "aws-amazonq-transform-step-into-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b6" + "fontCharacter": "\\f1bb" } }, "aws-amazonq-transform-step-into-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b7" + "fontCharacter": "\\f1bc" } }, "aws-amazonq-transform-variables-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b8" + "fontCharacter": "\\f1bd" } }, "aws-amazonq-transform-variables-light": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1b9" + "fontCharacter": "\\f1be" } }, "aws-applicationcomposer-icon": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ba" + "fontCharacter": "\\f1bf" } }, "aws-applicationcomposer-icon-dark": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1bb" + "fontCharacter": "\\f1c0" } }, "aws-apprunner-service": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1bc" + "fontCharacter": "\\f1c1" } }, "aws-cdk-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1bd" + "fontCharacter": "\\f1c2" } }, "aws-cloudformation-stack": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1be" + "fontCharacter": "\\f1c3" } }, "aws-cloudwatch-log-group": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1bf" + "fontCharacter": "\\f1c4" } }, "aws-codecatalyst-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c0" + "fontCharacter": "\\f1c5" } }, "aws-codewhisperer-icon-black": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c1" + "fontCharacter": "\\f1c6" } }, "aws-codewhisperer-icon-white": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c2" + "fontCharacter": "\\f1c7" } }, "aws-codewhisperer-learn": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c3" + "fontCharacter": "\\f1c8" } }, "aws-ecr-registry": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c4" + "fontCharacter": "\\f1c9" } }, "aws-ecs-cluster": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c5" + "fontCharacter": "\\f1ca" } }, "aws-ecs-container": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c6" + "fontCharacter": "\\f1cb" } }, "aws-ecs-service": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c7" + "fontCharacter": "\\f1cc" } }, "aws-generic-attach-file": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c8" + "fontCharacter": "\\f1cd" } }, "aws-iot-certificate": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1c9" + "fontCharacter": "\\f1ce" } }, "aws-iot-policy": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ca" + "fontCharacter": "\\f1cf" } }, "aws-iot-thing": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1cb" + "fontCharacter": "\\f1d0" } }, "aws-lambda-function": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1cc" + "fontCharacter": "\\f1d1" } }, "aws-mynah-MynahIconBlack": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1cd" + "fontCharacter": "\\f1d2" } }, "aws-mynah-MynahIconWhite": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1ce" + "fontCharacter": "\\f1d3" } }, "aws-mynah-logo": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1cf" + "fontCharacter": "\\f1d4" } }, "aws-redshift-cluster": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d0" + "fontCharacter": "\\f1d5" } }, "aws-redshift-cluster-connected": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d1" + "fontCharacter": "\\f1d6" } }, "aws-redshift-database": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d2" + "fontCharacter": "\\f1d7" } }, "aws-redshift-redshift-cluster-connected": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d3" + "fontCharacter": "\\f1d8" } }, "aws-redshift-schema": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d4" + "fontCharacter": "\\f1d9" } }, "aws-redshift-table": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d5" + "fontCharacter": "\\f1da" } }, "aws-s3-bucket": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d6" + "fontCharacter": "\\f1db" } }, "aws-s3-create-bucket": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d7" + "fontCharacter": "\\f1dc" } }, "aws-schemas-registry": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d8" + "fontCharacter": "\\f1dd" } }, "aws-schemas-schema": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1d9" + "fontCharacter": "\\f1de" } }, "aws-stepfunctions-preview": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", - "fontCharacter": "\\f1da" + "fontCharacter": "\\f1df" } } - }, - "notebooks": [ - { - "type": "aws-redshift-sql-notebook", - "displayName": "Redshift SQL notebook", - "selector": [ - { - "filenamePattern": "*.redshiftnb" - } - ] - } - ], - "customEditors": [ - { - "viewType": "threatComposer.tc.json", - "displayName": "%AWS.threatComposer.title%", - "selector": [ - { - "filenamePattern": "*.tc.json" - } - ] - } - ], - "configurationDefaults": { - "workbench.editorAssociations": { - "{git,gitlens,conflictResolution,vscode-local-history}:/**/*.tc.json": "default" - } } }, "scripts": { @@ -4057,17 +420,12 @@ "clean": "ts-node ../../scripts/clean.ts dist/", "copyFiles": "ts-node ./scripts/build/copyFiles.ts", "buildScripts": "npm run generateClients && npm run generateIcons && npm run copyFiles", - "compile": "npm run testCompile && webpack", + "compile": "npm run clean && npm run buildScripts && npm run compileOnly && webpack", "compileOnly": "tsc -p ./", "compileDev": "npm run compile -- --mode development", "webpackDev": "webpack --mode development", "serveVue": "webpack serve --config-name vue --mode development", - "watch": "npm run testCompile -- -- --watch", - "testCompile": "npm run clean && npm run buildScripts && npm run compileOnly", - "test": "npm run testCompile && c8 ts-node ./scripts/test/test.ts dist/src/test/index.js", - "testWeb": "npm run compileDev && c8 ts-node ./scripts/test/testWeb.ts", - "testE2E": "npm run testCompile && c8 ts-node ./scripts/test/testE2E.ts dist/src/testE2E/index.js", - "testInteg": "npm run testCompile && c8 ts-node ./scripts/test/testInteg.ts", + "watch": "npm run clean && npm run buildScripts && npm run compileOnly -- --watch", "lint": "ts-node ./scripts/lint/testLint.ts", "generateClients": "ts-node ./scripts/build/generateServiceClient.ts ", "generateIcons": "ts-node ../../scripts/generateIcons.ts", @@ -4086,13 +444,11 @@ "@cspotcode/source-map-support": "^0.8.1", "@sinonjs/fake-timers": "^10.0.2", "@types/adm-zip": "^0.4.34", - "@types/archiver": "^6.0.2", "@types/async-lock": "^1.4.0", "@types/bytes": "^3.1.0", "@types/circular-dependency-plugin": "^5.0.8", "@types/cross-spawn": "^6.0.6", "@types/diff": "^5.0.7", - "@types/fs-extra": "^9.0.11", "@types/glob": "^8.1.0", "@types/js-yaml": "^4.0.5", "@types/jsdom": "^21.1.6", @@ -4104,7 +460,7 @@ "@types/node-fetch": "^2.6.8", "@types/prismjs": "^1.26.0", "@types/proper-lockfile": "^4.1.4", - "@types/readline-sync": "^1.4.3", + "@types/readline-sync": "^1.4.8", "@types/semver": "^7.5.0", "@types/sinon": "^10.0.5", "@types/sinonjs__fake-timers": "^8.1.2", @@ -4117,7 +473,6 @@ "c8": "^9.0.0", "circular-dependency-plugin": "^5.2.2", "css-loader": "^6.10.0", - "diff": "^5.1.0", "esbuild-loader": "2.20.0", "file-loader": "^6.2.0", "jsdom": "^23.0.1", @@ -4128,7 +483,7 @@ "mocha-multi-reporters": "^1.5.1", "readline-sync": "^1.4.9", "sass": "^1.49.8", - "sass-loader": "^12.6.0", + "sass-loader": "^16.0.2", "sinon": "^14.0.0", "style-loader": "^3.3.1", "ts-node": "^10.9.1", @@ -4139,7 +494,10 @@ "webfont": "^11.2.26" }, "dependencies": { + "@amzn/amazon-q-developer-streaming-client": "file:../../src.gen/@amzn/amazon-q-developer-streaming-client", "@amzn/codewhisperer-streaming": "file:../../src.gen/@amzn/codewhisperer-streaming", + "@aws-sdk/client-cloudformation": "^3.667.0", + "@aws-sdk/client-cloudwatch-logs": "^3.666.0", "@aws-sdk/client-cognito-identity": "^3.637.0", "@aws-sdk/client-lambda": "^3.637.0", "@aws-sdk/client-sso": "^3.342.0", @@ -4150,7 +508,7 @@ "@aws-sdk/property-provider": "3.46.0", "@aws-sdk/smithy-client": "^3.46.0", "@aws-sdk/util-arn-parser": "^3.46.0", - "@aws/mynah-ui": "^4.15.11", + "@aws/mynah-ui": "^4.21.3", "@gerhobbelt/gitignore-parser": "^0.2.0-9", "@iarna/toml": "^2.2.5", "@smithy/middleware-retry": "^2.3.1", @@ -4159,17 +517,17 @@ "@smithy/shared-ini-file-loader": "^2.2.8", "@smithy/util-retry": "^2.2.0", "@vscode/debugprotocol": "^1.57.0", + "@zip.js/zip.js": "^2.7.41", "adm-zip": "^0.5.10", - "amazon-states-language-service": "^1.11.0", - "archiver": "^7.0.1", + "amazon-states-language-service": "^1.13.0", "async-lock": "^1.4.0", "aws-sdk": "^2.1384.0", "aws-ssm-document-language-service": "^1.0.0", "bytes": "^3.1.2", "cross-fetch": "^4.0.0", - "cross-spawn": "^7.0.3", + "cross-spawn": "^7.0.5", + "diff": "^5.1.0", "fast-json-patch": "^3.1.1", - "fs-extra": "^10.0.1", "glob": "^10.3.10", "got": "^11.8.5", "highlight.js": "^11.9.0", @@ -4183,8 +541,6 @@ "mime-types": "^2.1.32", "node-fetch": "^2.7.0", "portfinder": "^1.0.32", - "proper-lockfile": "^4.1.2", - "ps-list": "^8.1.1", "semver": "^7.5.4", "stream-buffers": "^3.0.2", "strip-ansi": "^5.2.0", diff --git a/packages/core/package.nls.json b/packages/core/package.nls.json index da644ab59ef..cab4f52dcee 100644 --- a/packages/core/package.nls.json +++ b/packages/core/package.nls.json @@ -5,6 +5,7 @@ "AWS.productName.cn": "Amazon Toolkit", "AWS.amazonq.productName": "Amazon Q", "AWS.codecatalyst.submenu.title": "Manage CodeCatalyst", + "AWS.notifications.title": "Notifications", "AWS.configuration.profileDescription": "The name of the credential profile to obtain credentials from.", "AWS.configuration.description.lambda.recentlyUploaded": "Recently selected Lambda upload targets.", "AWS.configuration.description.ecs.openTerminalCommand": "The command to run when starting a new interactive terminal session.", @@ -19,7 +20,7 @@ "AWS.configuration.description.suppressPrompts": "Prompts which ask for confirmation. Checking an item suppresses the prompt.", "AWS.configuration.enableCodeLenses": "Enable SAM hints in source code and template.yaml files", "AWS.configuration.description.resources.enabledResources": "AWS resources to display in the 'Resources' portion of the explorer.", - "AWS.configuration.description.experiments": "Try experimental features and give feedback. Note that experimental features may be removed at any time.\n * `jsonResourceModification` - Enables basic create, update, and delete support for cloud resources via the JSON Resources explorer component.\n * `samSyncCode` - Adds an additional code-only option when synchronizing SAM applications. Code-only synchronizations are faster but can cause drift in the CloudFormation stack. Does nothing when using the legacy SAM deploy feature.\n * `iamPolicyChecks` - Enables IAM Policy Checks feature, allowing users to validate IAM policies against IAM policy grammar, AWS best practices, and specified security standards.", + "AWS.configuration.description.experiments": "Try experimental features and give feedback. Note that experimental features may be removed at any time.\n * `jsonResourceModification` - Enables basic create, update, and delete support for cloud resources via the JSON Resources explorer component.\n * `ec2RemoteConnect` - Allows interfacing with EC2 instances with options to start, stop, and establish remote connections. Remote connections are done over SSM and can be through a terminal or a remote VSCode window.", "AWS.stepFunctions.asl.format.enable.desc": "Enables the default formatter used with Amazon States Language files", "AWS.stepFunctions.asl.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).", "AWS.configuration.description.awssam.debug.api": "API Gateway configuration", @@ -73,15 +74,25 @@ "AWS.configuration.description.amazonq": "Amazon Q creates a code reference when you insert a code suggestion from Amazon Q that is similar to training data. When unchecked, Amazon Q will not show code suggestions that have code references. If you authenticate through IAM Identity Center, this setting is controlled by your Amazon Q administrator. [Learn More](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-reference.html)", "AWS.configuration.description.amazonq.shareContentWithAWS": "When checked, your content processed by Amazon Q may be used for service improvement (except for content processed for users with the Amazon Q Developer Pro Tier). Unchecking this box will cause AWS to delete any of your content used for that purpose. The information used to provide the Amazon Q service to you will not be affected. See the [Service Terms](https://aws.amazon.com/service-terms) for more details.", "AWS.configuration.description.amazonq.importRecommendation": "Amazon Q will add import statements with inline code suggestions when necessary.", - "AWS.configuration.description.amazonq.workspaceIndex": "This feature is in BETA. When you add @workspace to your question in Amazon Q chat, Amazon Q will index your open workspace files locally to use as context for its response. Extra CPU usage is expected while indexing a workspace. This will not impact Amazon Q features or your IDE, but you may manage CPU usage by setting the number of local threads in 'Local Workspace Index Threads'.", + "AWS.configuration.description.amazonq.workspaceIndex": "When you add @workspace to your question in Amazon Q chat, Amazon Q will index your open workspace files locally to use as context for its response. Extra CPU usage is expected while indexing a workspace. This will not impact Amazon Q features or your IDE, but you may manage CPU usage by setting the number of local threads in 'Local Workspace Index Threads'.", "AWS.configuration.description.amazonq.workspaceIndexWorkerThreads": "Number of worker threads of Amazon Q local index process. '0' will use the system default worker threads for balance performance. You may increase this number to more quickly index your workspace, but only up to your hardware's number of CPU cores. Please restart VS Code or reload the VS Code window after changing worker threads.", "AWS.configuration.description.amazonq.workspaceIndexUseGPU": "Enable GPU to help index your local workspace files. Only applies to Linux and Windows.", "AWS.configuration.description.amazonq.workspaceIndexMaxSize": "The maximum size of local workspace files to be indexed in MB", + "AWS.configuration.description.amazonq.ignoredSecurityIssues": "Specifies a list of code issue identifiers that Amazon Q should ignore when reviewing your workspace. Each item in the array should be a unique string identifier for a specific code issue. This allows you to suppress notifications for known issues that you've assessed and determined to be false positives or not applicable to your project. Use this setting with caution, as it may cause you to miss important security alerts.", "AWS.command.apig.copyUrl": "Copy URL", - "AWS.command.apig.invokeRemoteRestApi": "Invoke on AWS", + "AWS.command.apig.invokeRemoteRestApi": "Invoke in the cloud", "AWS.command.apig.invokeRemoteRestApi.cn": "Invoke on Amazon", - "AWS.command.applicationComposer.open": "Open with Application Composer", - "AWS.command.applicationComposer.openDialog": "Open Template with Application Composer...", + "AWS.appBuilder.explorerTitle": "Application Builder", + "AWS.appBuilder.explorerNode.noApps": "[This resource is not yet supported.]", + "AWS.appBuilder.explorerNode.unavailableDeployedResource": "[Failed to retrive deployed resource.]", + "AWS.command.appBuilder.openHandler": "Open Function Handler", + "AWS.command.applicationComposer.open": "Open with Infrastructure Composer", + "AWS.command.appBuilder.openTemplate": "Open Template File", + "AWS.command.appBuilder.deploy": "Deploy SAM Application", + "AWS.command.appBuilder.build": "Build SAM Template", + "AWS.command.appBuilder.searchLogs": "Search Logs", + "AWS.command.refreshappBuilderExplorer": "Refresh Application Builder Explorer", + "AWS.command.applicationComposer.openDialog": "Open Template with Infrastructure Composer...", "AWS.command.auth.addConnection": "Add New Connection", "AWS.command.auth.showConnectionsPage": "Add New Connection", "AWS.command.auth.switchConnections": "Switch Connections", @@ -111,24 +122,34 @@ "AWS.command.amazonq.fixCode": "Fix", "AWS.command.amazonq.optimizeCode": "Optimize", "AWS.command.amazonq.sendToPrompt": "Send to prompt", - "AWS.command.amazonq.generateUnitTests": "Generate Tests (Beta)", - "AWS.command.amazonq.security.scan": "Run Project Scan", + "AWS.command.amazonq.generateUnitTests": "Generate Tests", + "AWS.command.amazonq.security.scan": "Run Project Review", + "AWS.command.amazonq.security.fileScan": "Run File Review", + "AWS.command.amazonq.generateFix": "Generate Fix", + "AWS.command.amazonq.viewDetails": "View Details", + "AWS.command.amazonq.explainIssue": "Explain", + "AWS.command.amazonq.ignoreIssue": "Ignore Issue", + "AWS.command.amazonq.ignoreAllIssues": "Ignore Similar Issues", + "AWS.command.amazonq.acceptFix": "Accept Fix", + "AWS.command.amazonq.regenerateFix": "Regenerate Fix", + "AWS.command.amazonq.filterIssues": "Filter Issues", "AWS.command.deploySamApplication": "Deploy SAM Application", "AWS.command.aboutToolkit": "About", "AWS.command.downloadLambda": "Download...", "AWS.command.uploadLambda": "Upload Lambda...", - "AWS.command.invokeLambda": "Invoke on AWS", + "AWS.command.invokeLambda": "Invoke in the cloud", "AWS.command.invokeLambda.cn": "Invoke on Amazon", "AWS.command.refreshAwsExplorer": "Refresh Explorer", "AWS.command.refreshCdkExplorer": "Refresh CDK Explorer", "AWS.command.cdk.help": "View CDK Documentation", - "AWS.command.ec2.openTerminal": "Open terminal to EC2 instance...", - "AWS.command.ec2.openRemoteConnection": "Connect to EC2 instance in New Window...", + "AWS.command.ec2.openTerminal": "Open Terminal to EC2 Instance...", + "AWS.command.ec2.openRemoteConnection": "Connect VS Code to EC2 Instance...", "AWS.command.ec2.startInstance": "Start EC2 Instance", "AWS.command.ec2.linkToLaunch": "Launch EC2 Instance", "AWS.command.ec2.stopInstance": "Stop EC2 Instance", "AWS.command.ec2.rebootInstance": "Reboot EC2 Instance", "AWS.command.ec2.copyInstanceId": "Copy Instance Id", + "AWS.command.ec2.viewLogs": "View EC2 Logs", "AWS.command.ecr.copyTagUri": "Copy Tag URI", "AWS.command.ecr.copyRepositoryUri": "Copy Repository URI", "AWS.command.ecr.createRepository": "Create Repository...", @@ -146,6 +167,8 @@ "AWS.command.renderStateMachineGraph": "Render graph", "AWS.command.copyArn": "Copy ARN", "AWS.command.copyName": "Copy Name", + "AWS.command.openAwsConsole": "Go to AWS management console", + "AWS.command.openAwsConsole.cn": "Go to Amazon management console", "AWS.command.listCommands": "Show AWS Commands...", "AWS.command.listCommands.cn": "Show Amazon Commands...", "AWS.command.downloadStateMachineDefinition": "Download Definition...", @@ -154,6 +177,7 @@ "AWS.command.downloadSchemaItemCode": "Download Code Bindings", "AWS.command.viewLogs": "View Logs", "AWS.command.cloudWatchLogs.searchLogGroup": "Search Log Group", + "AWS.command.cloudWatchLogs.tailLogGroup": "Tail Log Group", "AWS.command.sam.newTemplate": "Create new SAM Template", "AWS.command.cloudFormation.newTemplate": "Create new CloudFormation Template", "AWS.command.quickStart": "View Quick Start", @@ -196,8 +220,8 @@ "AWS.command.ssmDocument.openLocalDocumentJson": "Download as JSON", "AWS.command.ssmDocument.openLocalDocumentYaml": "Download as YAML", "AWS.command.ssmDocument.publishDocument": "Publish a Systems Manager Document", - "AWS.command.launchConfigForm.title": "Edit SAM Debug Configuration", - "AWS.command.addSamDebugConfig": "Add SAM Debug Configuration", + "AWS.command.launchConfigForm.title": "Local Invoke and Debug Configuration", + "AWS.command.addSamDebugConfig": "Add Local Invoke and Debug Configuration", "AWS.command.toggleSamCodeLenses": "Toggle SAM hints in source files", "AWS.command.apprunner.createService": "Create Service", "AWS.command.apprunner.createServiceFromEcr": "Create App Runner Service", @@ -227,13 +251,14 @@ "AWS.lambda.explorerTitle": "Explorer", "AWS.developerTools.explorerTitle": "Developer Tools", "AWS.codewhisperer.explorerTitle": "CodeWhisperer", - "AWS.appcomposer.explorerTitle": "Application Composer", + "AWS.appcomposer.explorerTitle": "Infrastructure Composer", "AWS.cdk.explorerTitle": "CDK", "AWS.codecatalyst.explorerTitle": "CodeCatalyst", - "AWS.cwl.limit.desc": "Maximum amount of log entries pulled per request from CloudWatch Logs (max 10000)", + "AWS.cwl.limit.desc": "Maximum amount of log entries pulled per request from CloudWatch Logs. For LiveTail, when the limit is reached, the oldest events will be removed to accomodate new events. (max 10000)", "AWS.samcli.deploy.bucket.recentlyUsed": "Buckets recently used for SAM deployments", "AWS.submenu.amazonqEditorContextSubmenu.title": "Amazon Q", "AWS.submenu.auth.title": "Authentication", + "AWS.submenu.amazonqSecurityIssueTree.filters": "Filter Issues", "AWS.generic.feedback": "Feedback", "AWS.generic.help": "Help", "AWS.generic.create": "Create...", @@ -244,6 +269,10 @@ "AWS.generic.promptUpdate": "Update...", "AWS.generic.preview": "Preview", "AWS.generic.viewDocs": "View Documentation", + "AWS.generic.moreActions": "More Actions...", + "AWS.generic.dismiss": "Dismiss", + "AWS.generic.cancel": "Cancel", + "AWS.generic.cancelling": "Cancelling...", "AWS.ssmDocument.ssm.maxItemsComputed.desc": "Controls the maximum number of problems produced by the SSM Document language server.", "AWS.walkthrough.gettingStarted.title": "Get started with AWS", "AWS.walkthrough.gettingStarted.description": "These walkthroughs help you set up the AWS Toolkit.", @@ -263,26 +292,40 @@ "AWS.codewhisperer.customization.notification.new_customizations.learn_more": "Learn More", "AWS.amazonq.title": "Amazon Q", "AWS.amazonq.chat": "Chat", + "AWS.amazonq.security": "Code Issues", "AWS.amazonq.login": "Login", "AWS.amazonq.learnMore": "Learn More About Amazon Q", + "AWS.amazonq.exploreAgents": "Explore Agent Capabilities", + "AWS.amazonq.welcomeWalkthrough": "Welcome Walkthrough", "AWS.amazonq.codewhisperer.title": "Amazon Q", "AWS.amazonq.toggleCodeSuggestion": "Toggle Auto-Suggestions", "AWS.amazonq.toggleCodeScan": "Toggle Auto-Scans", + "AWS.amazonq.scans.scanProgress": "Sure. This may take a few minutes. I will send a notification when it’s complete if you navigate away from this panel.", + "AWS.amazonq.scans.waitingForInput": "Waiting on your inputs...", + "AWS.amazonq.scans.chooseScan.description": "Would you like to review your active file or the workspace you have open?", + "AWS.amazonq.scans.runCodeScan": "Run a code review", + "AWS.amazonq.scans.projectScan": "Review workspace", + "AWS.amazonq.scans.fileScan": "Review active file", + "AWS.amazonq.scans.projectScanInProgress": "Workspace review is in progress...", + "AWS.amazonq.scans.fileScanInProgress": "File review is in progress...", + "AWS.amazonq.scans.noGitRepo": "Your workspace is not in a git repository. I'll review your project files for security issues, and your in-flight changes for code quality issues.", "AWS.amazonq.featureDev.error.conversationIdNotFoundError": "Conversation id must exist before starting code generation", "AWS.amazonq.featureDev.error.contentLengthError": "The folder you selected is too large for me to use as context. Please choose a smaller folder to work on. For more information on quotas, see the Amazon Q Developer documentation.", "AWS.amazonq.featureDev.error.illegalStateTransition": "Illegal transition between states, restart the conversation", "AWS.amazonq.featureDev.error.prepareRepoFailedError": "Sorry, I ran into an issue while trying to upload your code. Please try again.", "AWS.amazonq.featureDev.error.promptRefusalException": "I'm sorry, I can't generate code for your request. Please make sure your message and code files comply with the AWS Responsible AI Policy.", + "AWS.amazonq.featureDev.error.noChangeRequiredException": "I'm sorry, I ran into an issue while trying to generate your code.\n\n- `/dev` can generate code to make a change in your project. Provide a detailed description of the new feature or code changes you want to make, including the specifics of what the code should achieve.\n\n- To ask me to explain, debug, or optimize your code, you can close this chat tab to start a new conversation.", "AWS.amazonq.featureDev.error.zipFileError": "The zip file is corrupted", "AWS.amazonq.featureDev.error.codeIterationLimitError": "Sorry, you've reached the quota for number of iterations on code generation. You can insert this code in your files or discuss a new plan. For more information on quotas, see the Amazon Q Developer documentation.", "AWS.amazonq.featureDev.error.tabIdNotFoundError": "I'm sorry, I'm having technical difficulties at the moment. Please try again.", "AWS.amazonq.featureDev.error.codeGen.denyListedError": "I'm sorry, I'm having trouble generating your code and can't continue at the moment. Please try again later, and share feedback to help me improve.", "AWS.amazonq.featureDev.error.codeGen.default": "I'm sorry, I ran into an issue while trying to generate your code. Please try again.", "AWS.amazonq.featureDev.error.codeGen.timeout": "Code generation did not finish within the expected time", + "AWS.amazonq.featureDev.error.uploadURLExpired": "I’m sorry, I wasn't able to generate code. A connection timed out or became unavailable. Please try again or check the following:\n\n- Exclude non-essential files in your workspace’s `.gitignore.`\n\n- Check that your network connection is stable.", "AWS.amazonq.featureDev.error.workspaceFolderNotFoundError": "I couldn't find a workspace folder. Open a workspace, and then open a new chat tab and enter /dev to start discussing your code task with me.", "AWS.amazonq.featureDev.error.selectedFolderNotInWorkspaceFolderError": "The folder you chose isn't in your open workspace folder. You can add this folder to your workspace, or choose a folder in your open workspace.", "AWS.amazonq.featureDev.error.userMessageNotFoundError": "It looks like you didn't provide an input. Please enter your message in the text bar.", - "AWS.amazonq.featureDev.error.monthlyLimitReached": "You've reached the monthly quota for the Amazon Q agent for software development. You can try again next month. For more information on usage limits, see the Amazon Q Developer pricing page.", + "AWS.amazonq.featureDev.error.monthlyLimitReached": "You've reached the monthly quota for Amazon Q Developer's agent capabilities. You can try again next month. For more information on usage limits, see the Amazon Q Developer pricing page.", "AWS.amazonq.featureDev.error.technicalDifficulties": "I'm sorry, I'm having technical difficulties and can't continue at the moment. Please try again later, and share feedback to help me improve.", "AWS.amazonq.featureDev.error.throttling": "I'm sorry, I'm experiencing high demand at the moment and can't generate your code. This attempt won't count toward usage limits. Please try again.", "AWS.amazonq.featureDev.error.submitFeedback": "'submitFeedback' command was called programmatically, but its not registered.", @@ -296,6 +339,10 @@ "AWS.amazonq.featureDev.pillText.generatingCode": "Generating code...", "AWS.amazonq.featureDev.pillText.requestingChanges": "Requesting changes ...", "AWS.amazonq.featureDev.pillText.insertCode": "Accept code", + "AWS.amazonq.featureDev.pillText.continue": "Continue", + "AWS.amazonq.featureDev.pillText.acceptAllChanges": "Accept all changes", + "AWS.amazonq.featureDev.pillText.acceptRemainingChanges": "Accept remaining changes", + "AWS.amazonq.featureDev.pillText.stoppingCodeGeneration": "Stopping code generation...", "AWS.amazonq.featureDev.pillText.sendFeedback": "Send feedback", "AWS.amazonq.featureDev.pillText.selectFiles": "Select files for context", "AWS.amazonq.featureDev.pillText.retry": "Retry", @@ -305,15 +352,51 @@ "AWS.amazonq.featureDev.pillText.selectOption": "Choose an option to proceed", "AWS.amazonq.featureDev.pillText.unableGenerateChanges": "Unable to generate any file changes", "AWS.amazonq.featureDev.pillText.provideFeedback": "Provide feedback & regenerate", - "AWS.amazonq.featureDev.answer.generateSuggestion": "Would you like to generate a suggestion for this? You’ll review a file diff before inserting into your project.", + "AWS.amazonq.featureDev.answer.generateSuggestion": "Would you like to generate a suggestion for this? You'll review a file diff before inserting into your project.", "AWS.amazonq.featureDev.answer.qGeneratedCode": "The Amazon Q Developer Agent for software development has generated code for you to review", "AWS.amazonq.featureDev.answer.howCodeCanBeImproved": "How can I improve the code for your use case?", "AWS.amazonq.featureDev.answer.updateCode": "Okay, I updated your code files. Would you like to work on another task?", "AWS.amazonq.featureDev.answer.sessionClosed": "Okay, I've ended this chat session. You can open a new tab to chat or start another workflow.", "AWS.amazonq.featureDev.answer.newTaskChanges": "What new task would you like to work on?", "AWS.amazonq.featureDev.placeholder.chatInputDisabled": "Chat input is disabled", - "AWS.amazonq.featureDev.placeholder.additionalImprovements": "Choose an option to proceed", + "AWS.amazonq.featureDev.placeholder.additionalImprovements": "Describe your task or issue in detail", "AWS.amazonq.featureDev.placeholder.feedback": "Provide feedback or comments", "AWS.amazonq.featureDev.placeholder.describe": "Describe your task or issue in detail", - "AWS.amazonq.featureDev.placeholder.sessionClosed": "Open a new chat tab to continue" + "AWS.amazonq.featureDev.placeholder.sessionClosed": "Open a new chat tab to continue", + "AWS.amazonq.doc.pillText.selectOption": "Choose an option to continue", + "AWS.amazonq.doc.answer.createReadme": "Create a README for this project?", + "AWS.amazonq.doc.answer.updateReadme": "Update the README for this project?", + "AWS.amazonq.doc.answer.editReadme": "Okay, let's work on your README. Describe the changes you would like to make. For example, you can ask me to:\n- Correct something\n- Expand on something\n- Add a section\n- Remove a section", + "AWS.amazonq.doc.answer.readmeCreated": "I've created a README for your code.", + "AWS.amazonq.doc.answer.readmeUpdated": "I've updated your README.", + "AWS.amazonq.doc.answer.codeResult": "You can accept the changes to your files, or describe any additional changes you'd like me to make.", + "AWS.amazonq.doc.answer.scanning": "Scanning source files", + "AWS.amazonq.doc.answer.summarizing": "Summarizing source files", + "AWS.amazonq.doc.answer.generating": "Generating documentation", + "AWS.amazonq.doc.answer.creating": "Okay, I'm creating a README for your project. This may take a few minutes.", + "AWS.amazonq.doc.answer.updating": "Okay, I'm updating the README to reflect your code changes. This may take a few minutes.", + "AWS.amazonq.doc.answer.chooseFolder": "Choose a folder to continue.", + "AWS.amazonq.doc.error.noFolderSelected": "It looks like you didn't choose a folder. Choose a folder to continue.", + "AWS.amazonq.doc.error.contentLengthError": "Your workspace is too large for me to review. Your workspace must be within the quota, even if you choose a smaller folder. For more information on quotas, see the Amazon Q Developer documentation.", + "AWS.amazonq.doc.error.readmeTooLarge": "The README in your folder is too large for me to review. Try reducing the size of your README, or choose a folder with a smaller README. For more information on quotas, see the Amazon Q Developer documentation.", + "AWS.amazonq.doc.error.workspaceEmpty": "The folder you chose did not contain any source files in a supported language. Choose another folder and try again. For more information on supported languages, see the Amazon Q Developer documentation.", + "AWS.amazonq.doc.error.promptTooVague": "I need more information to make changes to your README. Try providing some of the following details:\n- Which sections you want to modify\n- The content you want to add or remove\n- Specific issues that need correcting\n\nFor more information on prompt best practices, see the Amazon Q Developer documentation.", + "AWS.amazonq.doc.error.promptUnrelated": "These changes don't seem related to documentation. Try describing your changes again, using the following best practices:\n- Changes should relate to how project functionality is reflected in the README\n- Content you refer to should be available in your codebase\n\n For more information on prompt best practices, see the Amazon Q Developer documentation.", + "AWS.amazonq.doc.error.docGen.default": "I'm sorry, I ran into an issue while trying to generate your documentation. Please try again.", + "AWS.amazonq.doc.error.noChangeRequiredException": "I couldn't find any code changes to update in the README. Try another documentation task.", + "AWS.amazonq.doc.error.promptRefusal": "I'm sorry, I can't generate documentation for this folder. Please make sure your message and code files comply with the Please make sure your message and code files comply with the AWS Responsible AI Policy.", + "AWS.amazonq.doc.placeholder.editReadme": "Describe documentation changes", + "AWS.amazonq.doc.pillText.closeSession": "End session", + "AWS.amazonq.inline.invokeChat": "Inline chat", + "AWS.toolkit.lambda.walkthrough.quickpickTitle": "Application Builder Walkthrough", + "AWS.toolkit.lambda.walkthrough.title": "Get started building your application", + "AWS.toolkit.lambda.walkthrough.description": "Your quick guide to build an application visually, iterate locally, and deploy to the cloud!", + "AWS.toolkit.lambda.walkthrough.toolInstall.title": "Complete installation", + "AWS.toolkit.lambda.walkthrough.toolInstall.description": "The AWS Command Line Interface (AWS CLI) is an open source tool that enables you to interact with AWS services using commands in your command-line shell. It is required to create and interact with AWS resources. \n\n[Install AWS CLI](command:aws.toolkit.installAWSCLI)\n\n Use the Serverless Application Model (SAM) CLI to locally build, invoke, and deploy your functions. Version 1.98+ is required. \n\n[Install SAM CLI](command:aws.toolkit.installSAMCLI)\n\n Use Docker to locally emulate a Lambda environment. Docker is optional. However, if you want to invoke locally, Docker is required so Lambda can locally emulate the execution environment. \n\n[Install Docker (optional)](command:aws.toolkit.installDocker)", + "AWS.toolkit.lambda.walkthrough.chooseTemplate.title": "Choose your application template", + "AWS.toolkit.lambda.walkthrough.chooseTemplate.description": "Select a starter application, visually compose an application from scratch, open an existing application, or browse more application examples. \n\nInfrastructure Composer allows you to visually compose modern applications in the cloud. It will define the necessary permissions between resources when you drag a connection between them. \n\n[Initialize your project](command:aws.toolkit.lambda.initializeWalkthroughProject)", + "AWS.toolkit.lambda.walkthrough.step1.title": "Iterate locally", + "AWS.toolkit.lambda.walkthrough.step1.description": "Locally test and debug your code.", + "AWS.toolkit.lambda.walkthrough.step2.title": "Deploy to the cloud", + "AWS.toolkit.lambda.walkthrough.step2.description": "Test your application in the cloud from within VS Code. \n\nNote: The AWS CLI and the SAM CLI require AWS Credentials to interact with the cloud. For information on setting up your credentials, see [Authentication and access credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html). \n\n[Configure credentials](command:aws.toolkit.lambda.walkthrough.credential)" } diff --git a/packages/core/resources/css/amazonq-chat.css b/packages/core/resources/css/amazonq-chat.css new file mode 100644 index 00000000000..5e954bba89f --- /dev/null +++ b/packages/core/resources/css/amazonq-chat.css @@ -0,0 +1,25 @@ +/* Pulsating effect */ +@keyframes pulsate { + 0% { + opacity: 0; + } + 50% { + opacity: 0.15; + } + 100% { + opacity: 0; + } +} + +body[data-feature-viewdiffinchat='TREATMENT'] { + .mynah-syntax-highlighter-copy-buttons button:nth-of-type(2)::after, /* Accept Diff and Insert At Cursor */ + .mynah-syntax-highlighter-copy-buttons button:nth-of-type(3)::after { + /* View Diff */ + content: ''; + animation-name: pulsate; + animation-duration: 1.5s; + animation-timing-function: ease-in-out; + animation-iteration-count: 2; + transform: translate3d(0%, 0, 0); + } +} diff --git a/packages/core/resources/css/amazonqTransformationHub.css b/packages/core/resources/css/amazonqTransformationHub.css index 57b4e5080d5..3deb855d087 100644 --- a/packages/core/resources/css/amazonqTransformationHub.css +++ b/packages/core/resources/css/amazonqTransformationHub.css @@ -73,7 +73,7 @@ body { } .status-FAILED { - color: red; + color: grey; } .substep { diff --git a/packages/core/resources/css/base.css b/packages/core/resources/css/base.css index 35a7cc736b9..eab33956f19 100644 --- a/packages/core/resources/css/base.css +++ b/packages/core/resources/css/base.css @@ -142,6 +142,7 @@ button, button:disabled { /* TODO: use VSC webcomponent library instead */ filter: brightness(0.8); + cursor: default; } /* Text area */ diff --git a/packages/core/resources/css/securityIssue.css b/packages/core/resources/css/securityIssue.css index f818ffdf14b..d766ccd8fe2 100644 --- a/packages/core/resources/css/securityIssue.css +++ b/packages/core/resources/css/securityIssue.css @@ -179,9 +179,10 @@ body.wordWrap pre { pre:not(.hljs), pre.hljs code > div { - padding: 0 16px 16px 16px; + padding: 16px; border-radius: 3px; overflow: auto; + margin-bottom: 0; } pre code { @@ -192,9 +193,11 @@ pre code { pre { background-color: var(--vscode-textCodeBlock-background); border: 1px solid var(--vscode-widget-border); + font-size: 12px; + line-height: 1rem; } -code.language-diff { +code[class^='language-'] { background-color: unset; } @@ -222,8 +225,164 @@ code.language-diff { color: var(--vscode-editorOverviewRuler-selectionHighlightForeground); display: inline-block; width: 100%; - margin: 0 -16px; - padding: 8px 16px; + margin: 0 -16px 4px -16px; + padding: 4px 16px; +} + +.hljs-meta * { + color: unset !important; +} + +.hljs-keyword, +.hljs-literal, +.hljs-symbol, +.hljs-name { + color: #569cd6; +} +.hljs-link { + color: #569cd6; + text-decoration: underline; +} + +.hljs-built_in, +.hljs-type { + color: #4ec9b0; +} + +.hljs-number, +.hljs-class { + color: #b8d7a3; +} + +.hljs-string, +.hljs-meta-string { + color: #d69d85; +} + +.hljs-regexp, +.hljs-template-tag { + color: #9a5334; +} + +.hljs-subst, +.hljs-function, +.hljs-title, +.hljs-params, +.hljs-formula { + color: #dcdcdc; +} + +.hljs-comment, +.hljs-quote { + color: #57a64a; + font-style: italic; +} + +.hljs-doctag { + color: #608b4e; +} + +.hljs-meta-keyword, +.hljs-tag { + color: #9b9b9b; +} + +.hljs-variable, +.hljs-template-variable { + color: #bd63c5; +} + +.hljs-attr, +.hljs-attribute, +.hljs-builtin-name { + color: #9cdcfe; +} + +.hljs-section { + color: gold; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-bullet, +.hljs-selector-tag, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #d7ba7d; +} + +.vscode-light .hljs-function, +.vscode-light .hljs-params, +.vscode-light .hljs-number, +.vscode-light .hljs-class { + color: inherit; +} + +.vscode-light .hljs-comment, +.vscode-light .hljs-quote, +.vscode-light .hljs-number, +.vscode-light .hljs-class, +.vscode-light .hljs-variable { + color: #008000; +} + +.vscode-light .hljs-keyword, +.vscode-light .hljs-selector-tag, +.vscode-light .hljs-name, +.vscode-light .hljs-tag { + color: #00f; +} + +.vscode-light .hljs-built_in, +.vscode-light .hljs-builtin-name { + color: #007acc; +} + +.vscode-light .hljs-string, +.vscode-light .hljs-section, +.vscode-light .hljs-attribute, +.vscode-light .hljs-literal, +.vscode-light .hljs-template-tag, +.vscode-light .hljs-template-variable, +.vscode-light .hljs-type { + color: #a31515; +} + +.vscode-light .hljs-subst, +.vscode-light .hljs-selector-attr, +.vscode-light .hljs-selector-pseudo, +.vscode-light .hljs-meta-keyword { + color: #2b91af; +} +.vscode-light .hljs-title, +.vscode-light .hljs-doctag { + color: #808080; +} + +.vscode-light .hljs-attr { + color: #f00; +} + +.vscode-light .hljs-symbol, +.vscode-light .hljs-bullet, +.vscode-light .hljs-link { + color: #00b0e8; +} + +.vscode-light .hljs-emphasis { + font-style: italic; +} + +.vscode-light .hljs-strong { + font-weight: bold; } input[type='submit'] { @@ -270,3 +429,203 @@ hr { img.severity { height: 0.75em; } + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.spinner { + display: inline-block; + animation: spin 1s infinite; +} + +button.button-theme-primary, +button.button-theme-secondary { + padding: 6px 14px; + border-radius: 5px; +} + +.code-diff-actions { + width: 100%; + height: 26px; + background-color: var(--vscode-editorMarkerNavigationInfo-headerBackground); + border-radius: 0 0 3px 3px; + overflow: auto; + display: flex; + flex-direction: row-reverse; +} + +.code-diff-action-button { + font-size: 12px; + padding: 1px 6.5px; + margin: 1px 0; + border-radius: 3px; + color: currentColor; + background-color: rgba(0, 0, 0, 0); + transition: all 600ms cubic-bezier(0.25, 1, 0, 1); + transform: translate3d(0, 0, 0) scale(1.00001); + gap: calc(0.25rem * 1); + filter: brightness(0.925); + border: none; +} + +.code-diff-action-button:hover { + filter: brightness(1); +} + +.code-diff-action-button:hover:after { + transform: translate3d(0%, 0, 0); + opacity: 0.15; +} + +.code-diff-action-button::after { + content: ''; + pointer-events: none; + transition: all 600ms cubic-bezier(0.25, 1, 0, 1); + opacity: 0; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + filter: brightness(1.35) saturate(0.75); + border-radius: inherit; + background-color: currentColor; + transform: translate3d(-5%, 0, 0) scale(0.93); +} + +.container-bottom { + bottom: 0; + justify-content: unset; + border-top: 1px solid var(--vscode-menu-separatorBackground); + border-bottom: none; +} + +.button-container { + padding: 10px 0; + flex-wrap: wrap; +} + +.button-container > button { + white-space: nowrap; + margin-bottom: 8px; +} + +pre.center { + display: flex; + justify-content: center; + padding-top: 16px; +} + +pre.error { + color: var(--vscode-diffEditorOverview-removedForeground); +} + +.dot-typing { + position: relative; + left: -9999px; + width: 8px; + height: 8px; + border-radius: 5px; + background-color: var(--vscode-editor-foreground); + color: var(--vscode-editor-foreground); + box-shadow: + 9984px 0 0 0 var(--vscode-editor-foreground), + 9999px 0 0 0 var(--vscode-editor-foreground), + 10014px 0 0 0 var(--vscode-editor-foreground); + animation: dot-typing 1.5s infinite linear; +} + +@keyframes dot-typing { + 0% { + box-shadow: + 9984px 0 0 0 var(--vscode-editor-foreground), + 9999px 0 0 0 var(--vscode-editor-foreground), + 10014px 0 0 0 var(--vscode-editor-foreground); + } + 16.667% { + box-shadow: + 9984px -10px 0 0 var(--vscode-editor-foreground), + 9999px 0 0 0 var(--vscode-editor-foreground), + 10014px 0 0 0 var(--vscode-editor-foreground); + } + 33.333% { + box-shadow: + 9984px 0 0 0 var(--vscode-editor-foreground), + 9999px 0 0 0 var(--vscode-editor-foreground), + 10014px 0 0 0 var(--vscode-editor-foreground); + } + 50% { + box-shadow: + 9984px 0 0 0 var(--vscode-editor-foreground), + 9999px -10px 0 0 var(--vscode-editor-foreground), + 10014px 0 0 0 var(--vscode-editor-foreground); + } + 66.667% { + box-shadow: + 9984px 0 0 0 var(--vscode-editor-foreground), + 9999px 0 0 0 var(--vscode-editor-foreground), + 10014px 0 0 0 var(--vscode-editor-foreground); + } + 83.333% { + box-shadow: + 9984px 0 0 0 var(--vscode-editor-foreground), + 9999px 0 0 0 var(--vscode-editor-foreground), + 10014px -10px 0 0 var(--vscode-editor-foreground); + } + 100% { + box-shadow: + 9984px 0 0 0 var(--vscode-editor-foreground), + 9999px 0 0 0 var(--vscode-editor-foreground), + 10014px 0 0 0 var(--vscode-editor-foreground); + } +} + +.code-block pre { + border-radius: 3px 3px 0 0; +} + +.line-number { + display: inline-block; + color: var(--vscode-editorOverviewRuler-selectionHighlightForeground); + width: 16px; + margin-right: 24px; + text-align: right; +} + +.highlight { + display: inline-block; + background-color: var(--vscode-editorOverviewRuler-selectionHighlightForeground); +} + +.reference-tracker { + cursor: help; +} + +.reference-tracker .tooltip { + visibility: hidden; + opacity: 0; + transition: + visibility 0s 0.1s, + opacity 0.1s linear; + position: absolute; + background-color: var(--vscode-editor-background); + color: var(--vscode-editor-foreground); + border: 1px solid var(--vscode-menu-separatorBackground); + border-radius: 5px; + padding: 16px; + margin-top: -80px; + margin-left: 20px; + cursor: default; +} + +.reference-tracker:hover .tooltip { + visibility: visible; + opacity: 1; + transition: opacity 0.1s linear; +} diff --git a/packages/core/resources/ec2_connect b/packages/core/resources/ec2_connect index 518a5e86f58..06ae8d39686 100755 --- a/packages/core/resources/ec2_connect +++ b/packages/core/resources/ec2_connect @@ -2,7 +2,7 @@ # Usage: # When connecting to a dev environment -# AWS_REGION=… AWS_SSM_CLI=… STREAM_URL=… TOKEN=… LOG_FILE_LOCATION==… ./ec2_connect +# AWS_REGION=… AWS_SSM_CLI=… STREAM_URL=… TOKEN=… LOG_FILE_LOCATION==… DEBUG_LOG=… ./ec2_connect set -e set -u @@ -44,13 +44,21 @@ _ec2() { _main() { _log "==============================================================================" - - _require AWS_SSM_CLI "${AWS_SSM_CLI:-}" + _require DEBUG_LOG "${DEBUG_LOG:-}" _require AWS_REGION "${AWS_REGION:-}" - _require STREAM_URL "${STREAM_URL:-}" - _require TOKEN "${TOKEN:-}" + _require SESSION_ID "${SESSION_ID:-}" - _require LOG_FILE_LOCATION "${LOG_FILE_LOCATION:-}" + _require_nolog STREAM_URL "${STREAM_URL:-}" + _require_nolog TOKEN "${TOKEN:-}" + + # Only log file paths when debug level is enabled. + if [ "${DEBUG_LOG:-}" -eq 1 ]; then + _require AWS_SSM_CLI "${AWS_SSM_CLI:-}" + _require LOG_FILE_LOCATION "${LOG_FILE_LOCATION:-}" + else + _require_nolog AWS_SSM_CLI "${AWS_SSM_CLI:-}" + _require_nolog LOG_FILE_LOCATION "${LOG_FILE_LOCATION:-}" + fi _ec2 "$AWS_SSM_CLI" "$AWS_REGION" "$STREAM_URL" "$TOKEN" "$SESSION_ID" } diff --git a/packages/core/resources/icons/aws/amazonq/severity-critical.svg b/packages/core/resources/icons/aws/amazonq/severity-critical.svg new file mode 100644 index 00000000000..7733994d24e --- /dev/null +++ b/packages/core/resources/icons/aws/amazonq/severity-critical.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/resources/icons/aws/amazonq/severity-high.svg b/packages/core/resources/icons/aws/amazonq/severity-high.svg new file mode 100644 index 00000000000..ff92aebc817 --- /dev/null +++ b/packages/core/resources/icons/aws/amazonq/severity-high.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/resources/icons/aws/amazonq/severity-info.svg b/packages/core/resources/icons/aws/amazonq/severity-info.svg new file mode 100644 index 00000000000..dbf78609170 --- /dev/null +++ b/packages/core/resources/icons/aws/amazonq/severity-info.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/resources/icons/aws/amazonq/severity-low.svg b/packages/core/resources/icons/aws/amazonq/severity-low.svg new file mode 100644 index 00000000000..4ca6d96961e --- /dev/null +++ b/packages/core/resources/icons/aws/amazonq/severity-low.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/resources/icons/aws/amazonq/severity-medium.svg b/packages/core/resources/icons/aws/amazonq/severity-medium.svg new file mode 100644 index 00000000000..a906d9b4873 --- /dev/null +++ b/packages/core/resources/icons/aws/amazonq/severity-medium.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core/resources/icons/aws/applicationcomposer/icon-dark.svg b/packages/core/resources/icons/aws/applicationcomposer/icon-dark.svg index 632d645338e..c631f327ae0 100644 --- a/packages/core/resources/icons/aws/applicationcomposer/icon-dark.svg +++ b/packages/core/resources/icons/aws/applicationcomposer/icon-dark.svg @@ -2,6 +2,6 @@ Icon-Service/16/AWS-Application-Composer_16_White - + diff --git a/packages/core/resources/icons/aws/applicationcomposer/icon.svg b/packages/core/resources/icons/aws/applicationcomposer/icon.svg index f2b405cfe57..65580136ce7 100644 --- a/packages/core/resources/icons/aws/applicationcomposer/icon.svg +++ b/packages/core/resources/icons/aws/applicationcomposer/icon.svg @@ -2,6 +2,6 @@ Icon-Service/16/AWS-Application-Composer_16 - + diff --git a/packages/core/resources/js/graphStateMachine.js b/packages/core/resources/js/graphStateMachine.js index d2ec7ca11ab..9ccff2145fa 100644 --- a/packages/core/resources/js/graphStateMachine.js +++ b/packages/core/resources/js/graphStateMachine.js @@ -108,7 +108,7 @@ zoomoutBtn.addEventListener('click', () => { // Message passing from extension to webview. // Capture state machine definition -window.addEventListener('message', event => { +window.addEventListener('message', (event) => { // event.data is object passed in from postMessage from vscode const message = event.data switch (message.command) { diff --git a/packages/core/resources/markdown/samReadme.md b/packages/core/resources/markdown/samReadme.md index e98229d9b5c..14022174844 100644 --- a/packages/core/resources/markdown/samReadme.md +++ b/packages/core/resources/markdown/samReadme.md @@ -13,9 +13,9 @@ ${LISTOFCONFIGURATIONS} You can debug the Lambda handlers locally by adding a breakpoint to the source file, then running the launch configuration. This works by using Docker on your local machine. -Invocation parameters, including payloads and request parameters, can be edited either by the `Edit SAM Debug Configuration` command (through the ${COMMANDPALETTE} or ${CODELENS}) or by editing the `launch.json` file. +Invocation parameters, including payloads and request parameters, can be edited either by the `Local Invoke and Debug Configuration` command (through the ${COMMANDPALETTE} or ${CODELENS}) or by editing the `launch.json` file. -${COMPANYNAME} Lambda functions not defined in the [`template.yaml`](./template.yaml) file can be invoked and debugged by creating a launch configuration through the ${CODELENS} over the function declaration, or with the `Add SAM Debug Configuration` command. +${COMPANYNAME} Lambda functions not defined in the [`template.yaml`](./template.yaml) file can be invoked and debugged by creating a launch configuration through the ${CODELENS} over the function declaration, or with the `Add Local Invoke and Debug Configuration` command. ## Deploying Serverless Applications diff --git a/packages/core/resources/walkthrough/appBuilder/AppPicker.md b/packages/core/resources/walkthrough/appBuilder/AppPicker.md new file mode 100644 index 00000000000..dbb78f0108d --- /dev/null +++ b/packages/core/resources/walkthrough/appBuilder/AppPicker.md @@ -0,0 +1,25 @@ + +
+ + + Rest API + + + + File processing + +
+
+ + + New template with visual builder + + + + Current workspace template + +
+
+ + See more application example... + diff --git a/packages/core/resources/walkthrough/appBuilder/AppPickerResource/API.png b/packages/core/resources/walkthrough/appBuilder/AppPickerResource/API.png new file mode 100644 index 00000000000..3cda59fbf48 Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/AppPickerResource/API.png differ diff --git a/packages/core/resources/walkthrough/appBuilder/AppPickerResource/AppComposer.png b/packages/core/resources/walkthrough/appBuilder/AppPickerResource/AppComposer.png new file mode 100644 index 00000000000..51ceb392f05 Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/AppPickerResource/AppComposer.png differ diff --git a/packages/core/resources/walkthrough/appBuilder/AppPickerResource/CustomTemplate.png b/packages/core/resources/walkthrough/appBuilder/AppPickerResource/CustomTemplate.png new file mode 100644 index 00000000000..6ebe6fe27a6 Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/AppPickerResource/CustomTemplate.png differ diff --git a/packages/core/resources/walkthrough/appBuilder/AppPickerResource/S3.png b/packages/core/resources/walkthrough/appBuilder/AppPickerResource/S3.png new file mode 100644 index 00000000000..7ea7eebd23b Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/AppPickerResource/S3.png differ diff --git a/packages/core/resources/walkthrough/appBuilder/InnerLoop.md b/packages/core/resources/walkthrough/appBuilder/InnerLoop.md new file mode 100644 index 00000000000..97f54e9dbca --- /dev/null +++ b/packages/core/resources/walkthrough/appBuilder/InnerLoop.md @@ -0,0 +1,12 @@ +

Build your code

+Compile your code and install dependencies with SAM CLI so you can invoke it locally. +clicking build icon in AppBuilder sidebar on project node +

Select function to invoke

+Find the function you want to invoke in Application Builder and use the icon to open the invoke and debug view. +clicking invoke function icon in AppBuilder sidebar on Lambda function node +

Invoke your function

+Configure a payload to use for invoking your function. +clicking invoke function button in local invoke webview +

View your execution results

+The VS Code panel will display the results of your invocation. +View your execution results in the output panel diff --git a/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-1.jpg b/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-1.jpg new file mode 100644 index 00000000000..b1236191118 Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-1.jpg differ diff --git a/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-2.jpg b/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-2.jpg new file mode 100644 index 00000000000..d7c0295e5ce Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-2.jpg differ diff --git a/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-3.jpg b/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-3.jpg new file mode 100644 index 00000000000..471cf590830 Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-3.jpg differ diff --git a/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-4.jpg b/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-4.jpg new file mode 100644 index 00000000000..c0df6db61b0 Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/InnerLoopResource/walkthrough-local-4.jpg differ diff --git a/packages/core/resources/walkthrough/appBuilder/RemoteLoop.md b/packages/core/resources/walkthrough/appBuilder/RemoteLoop.md new file mode 100644 index 00000000000..f811771127b --- /dev/null +++ b/packages/core/resources/walkthrough/appBuilder/RemoteLoop.md @@ -0,0 +1,12 @@ +

Deploy your application

+Use SAM CLI to deploy your application template to the cloud. +Click Deploy SAM Application button in Appbuilder Sidebar on project node +

Select deployed function to invoke

+Find the function you want to invoke in Application Builder and use the icon to open the remote invocation view. +click Invoke in the cloud button in AppBuilder Sidebar on cloud function resource node +

Invoke your function with a payload

+Configure a payload to use for invoking your function. +add a payload and click invoke button in remote invoke panel +

View your execution result in the output panel

+The VS Code panel will display the results of your invocation. +View your execution result in the output panel diff --git a/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-1.jpg b/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-1.jpg new file mode 100644 index 00000000000..c036fc93eaf Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-1.jpg differ diff --git a/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-2.jpg b/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-2.jpg new file mode 100644 index 00000000000..e542edc38f4 Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-2.jpg differ diff --git a/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-3.jpg b/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-3.jpg new file mode 100644 index 00000000000..891cfdc4aa0 Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-3.jpg differ diff --git a/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-4.jpg b/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-4.jpg new file mode 100644 index 00000000000..738a7d0b91f Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/RemoteLoopResource/walkthrough-remote-4.jpg differ diff --git a/packages/core/resources/walkthrough/appBuilder/install.png b/packages/core/resources/walkthrough/appBuilder/install.png new file mode 100644 index 00000000000..cdbae8b83a6 Binary files /dev/null and b/packages/core/resources/walkthrough/appBuilder/install.png differ diff --git a/packages/core/resources/walkthrough/setup-connect.md b/packages/core/resources/walkthrough/setup-connect.md index 44a1584838a..e78f612f4f7 100644 --- a/packages/core/resources/walkthrough/setup-connect.md +++ b/packages/core/resources/walkthrough/setup-connect.md @@ -12,7 +12,7 @@ Choose the most appropriate method based on your requirements. ## Connect to AWS through the Toolkit for VS Code -1. [Click here](command:aws.login) to open the configuration wizard to connect to AWS. +1. [Click here](command:aws.toolkit.login) to open the configuration wizard to connect to AWS. > This command can also be accessed through the [Command Palette](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/toolkit-navigation.html#command-locations) by choosing **AWS: >Connect to AWS**\. > diff --git a/packages/core/resources/walkthrough/setup-region.md b/packages/core/resources/walkthrough/setup-region.md index 292eb1ad220..50a9101e6bb 100644 --- a/packages/core/resources/walkthrough/setup-region.md +++ b/packages/core/resources/walkthrough/setup-region.md @@ -4,7 +4,7 @@ When you set up your credentials, the AWS Toolkit for Visual Studio Code automat ## Add a Region to the AWS Explorer -1. [Click here](command:aws.showRegion) to select a Region to add or remove. +1. [Click here](command:aws.toolkit.showRegion) to select a Region to add or remove. > This command can also be accessed through the [Command Palette](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/toolkit-navigation.html#command-locations) by choosing **AWS: Show or Hide Regions**\. > diff --git a/packages/core/scripts/build/copyFiles.ts b/packages/core/scripts/build/copyFiles.ts index 9fe1c5b13fa..e926a6d9963 100644 --- a/packages/core/scripts/build/copyFiles.ts +++ b/packages/core/scripts/build/copyFiles.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as fs from 'fs-extra' +/* eslint-disable no-restricted-imports */ +import fs from 'fs' import * as path from 'path' // Moves all dependencies into `dist` @@ -45,27 +46,28 @@ const tasks: CopyTask[] = [ }, ] -async function copy(task: CopyTask): Promise { +function copy(task: CopyTask): void { const src = path.resolve(projectRoot, task.target) const dst = path.resolve(outRoot, task.destination ?? task.target) try { - await fs.copy(src, dst, { + fs.cpSync(src, dst, { recursive: true, - overwrite: true, + force: true, errorOnExist: false, }) } catch (error) { throw new Error(`Copy "${src}" to "${dst}" failed: ${error instanceof Error ? error.message : error}`) } } - -void (async () => { +function main() { try { - await Promise.all(tasks.map(copy)) + tasks.map(copy) } catch (error) { console.error('`copyFiles.ts` failed') console.error(error) process.exit(1) } -})() +} + +void main() diff --git a/packages/core/scripts/build/generateServiceClient.ts b/packages/core/scripts/build/generateServiceClient.ts index 3e010400c4e..4c6fb730b19 100644 --- a/packages/core/scripts/build/generateServiceClient.ts +++ b/packages/core/scripts/build/generateServiceClient.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as proc from 'child_process' -import * as fs from 'fs-extra' +import * as proc from 'child_process' // eslint-disable-line no-restricted-imports +import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports import * as path from 'path' const repoRoot = path.join(process.cwd(), '../../') // root/packages/toolkit -> root/ @@ -33,7 +33,7 @@ async function generateServiceClients(serviceClientDefinitions: ServiceClientDef /** When cloning aws-sdk-js, we want to pull the version actually used in package-lock.json. */ function getJsSdkVersion(): string { - const json = fs.readFileSync(path.resolve(repoRoot, 'package-lock.json')).toString() + const json = nodefs.readFileSync(path.resolve(repoRoot, 'package-lock.json')).toString() const packageLock = JSON.parse(json) return packageLock['packages']['node_modules/aws-sdk']['version'] @@ -115,7 +115,7 @@ async function insertServiceClientsIntoJsSdk( 'apis', `${serviceClientDefinition.serviceName.toLowerCase()}-${apiVersion}.normal.json` ) - fs.copyFileSync(serviceClientDefinition.serviceJsonPath, jsSdkServiceJsonPath) + nodefs.copyFileSync(serviceClientDefinition.serviceJsonPath, jsSdkServiceJsonPath) }) const apiMetadataPath = path.join(jsSdkPath, 'apis', 'metadata.json') @@ -132,7 +132,7 @@ interface ServiceJsonSchema { } function getApiVersion(serviceJsonPath: string): string { - const json = fs.readFileSync(serviceJsonPath).toString() + const json = nodefs.readFileSync(serviceJsonPath).toString() const serviceJson = JSON.parse(json) as ServiceJsonSchema return serviceJson.metadata.apiVersion @@ -148,14 +148,14 @@ interface ApiMetadata { async function patchServicesIntoApiMetadata(apiMetadataPath: string, serviceNames: string[]): Promise { console.log(`Patching services (${serviceNames.join(', ')}) into API Metadata...`) - const apiMetadataJson = fs.readFileSync(apiMetadataPath).toString() + const apiMetadataJson = nodefs.readFileSync(apiMetadataPath).toString() const apiMetadata = JSON.parse(apiMetadataJson) as ApiMetadata serviceNames.forEach((serviceName) => { apiMetadata[serviceName.toLowerCase()] = { name: serviceName } }) - fs.writeFileSync(apiMetadataPath, JSON.stringify(apiMetadata, undefined, 4)) + nodefs.writeFileSync(apiMetadataPath, JSON.stringify(apiMetadata, undefined, 4)) } /** @@ -197,7 +197,7 @@ async function integrateServiceClient(repoPath: string, serviceJsonPath: string, console.log(`Integrating ${typingsFilename} ...`) - fs.copyFileSync(sourceClientPath, destinationClientPath) + nodefs.copyFileSync(sourceClientPath, destinationClientPath) await sanitizeServiceClient(destinationClientPath) } @@ -208,7 +208,7 @@ async function integrateServiceClient(repoPath: string, serviceJsonPath: string, async function sanitizeServiceClient(generatedClientPath: string): Promise { console.log('Altering Service Client to fit the codebase...') - let fileContents = fs.readFileSync(generatedClientPath).toString() + let fileContents = nodefs.readFileSync(generatedClientPath).toString() // Add a header stating the file is autogenerated fileContents = ` @@ -222,7 +222,7 @@ ${fileContents} fileContents = fileContents.replace(/(import .* from.*)\.\.(.*)/g, '$1aws-sdk$2') - fs.writeFileSync(generatedClientPath, fileContents) + nodefs.writeFileSync(generatedClientPath, fileContents) } // --------------------------------------------------------------------------------------------------------------------- diff --git a/packages/core/scripts/test/launchTest.ts b/packages/core/scripts/test/launchTest.ts new file mode 100644 index 00000000000..27ac83bd43f --- /dev/null +++ b/packages/core/scripts/test/launchTest.ts @@ -0,0 +1,37 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * This script is used to run unit tests from an npm script defined in package.json. + * TODO: Generalize and use named args system from root/scripts/package.ts + * + * Usage: + * node ./test/runToolkitTests.js + * + * where: + * - suite_name, e.g. 'unit', 'integration', 'e2e' + * - relative path to entrypoint: test entry point file built in .js, e.g. 'amazonq/dist/test/unit/index.js' + * - relative path to workspace folder: folder to open the test VSC instance in (optional) + * + * See examples in any subproject's package.json `test` scripts. + */ + +import 'source-map-support/register' + +import { runToolkitTests, SuiteName } from './launchTestUtilities' +void (async () => { + const suiteName = process.argv[2] as SuiteName + if (!suiteName) { + throw new Error('A test suite name is required.') + } + + const relativeEntrypoint = process.argv[3] + if (!relativeEntrypoint) { + throw new Error('A path relative to core is required.') + } + + const relativeWorkspaceFolder = process.argv[4] + await runToolkitTests(suiteName, relativeEntrypoint, relativeWorkspaceFolder) +})() diff --git a/packages/core/scripts/test/launchTestUtilities.ts b/packages/core/scripts/test/launchTestUtilities.ts index 2e73c8e5d4e..92afb769275 100644 --- a/packages/core/scripts/test/launchTestUtilities.ts +++ b/packages/core/scripts/test/launchTestUtilities.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as proc from 'child_process' +import * as proc from 'child_process' // eslint-disable-line no-restricted-imports import packageJson from '../../package.json' import { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } from '@vscode/test-electron' import { join, resolve } from 'path' @@ -18,7 +18,8 @@ const minimum = 'minimum' const disableWorkspaceTrust = '--disable-workspace-trust' -type SuiteName = 'integration' | 'e2e' | 'unit' | 'web' +const suiteNames = ['integration', 'e2e', 'unit', 'web'] as const +export type SuiteName = (typeof suiteNames)[number] /** * This is the generalized method that is used by different test suites (unit, integration, ...) in CI to @@ -35,6 +36,10 @@ export async function runToolkitTests( env?: Record ) { try { + if (!suiteNames.includes(suite)) { + throw new Error(`Invalid suite name: '${suite}'. Must be one of: ${suiteNames.join(',')}`) + } + console.log(`Running ${suite} test suite...`) const args = await getVSCodeCliArgs({ @@ -177,13 +182,16 @@ async function invokeVSCodeCli(vsCodeExecutablePath: string, args: string[]): Pr } console.log(`Invoking vscode CLI command:\n "${cli}" ${JSON.stringify(cmdArgs)}`) + // Shell option must be true on windows to avoid security error: https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2 const spawnResult = proc.spawnSync(cli, cmdArgs, { encoding: 'utf-8', stdio: 'pipe', + shell: process.platform === 'win32', }) if (spawnResult.status !== 0) { console.log('output: %s', spawnResult.output) + console.log('error: %O', spawnResult.error) throw new Error(`VS Code CLI command failed (exit-code: ${spawnResult.status}): ${cli} ${cmdArgs}`) } diff --git a/packages/core/scripts/test/test.ts b/packages/core/scripts/test/test.ts deleted file mode 100644 index c18a9f7036c..00000000000 --- a/packages/core/scripts/test/test.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { runToolkitTests } from './launchTestUtilities' -void (async () => { - const relativeEntrypoint = process.argv[2] - if (!relativeEntrypoint) { - throw new Error('A path relative to core is required') - } - - const relativeWorkspaceFolder = process.argv[3] - await runToolkitTests('unit', relativeEntrypoint, relativeWorkspaceFolder) -})() diff --git a/packages/core/scripts/test/testE2E.ts b/packages/core/scripts/test/testE2E.ts deleted file mode 100644 index 95aff8fa28b..00000000000 --- a/packages/core/scripts/test/testE2E.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { runToolkitTests } from './launchTestUtilities' -void (async () => { - const relativeEntrypoint = process.argv[2] - if (!relativeEntrypoint) { - throw new Error('Relative entrypoint is required') - } - - const relativeWorkspaceFolder = process.argv[3] - await runToolkitTests('e2e', relativeEntrypoint, relativeWorkspaceFolder) -})() diff --git a/packages/core/scripts/test/testInteg.ts b/packages/core/scripts/test/testInteg.ts deleted file mode 100644 index 2fc412c3ba5..00000000000 --- a/packages/core/scripts/test/testInteg.ts +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { runToolkitTests } from './launchTestUtilities' -void (async () => { - await runToolkitTests('integration', 'dist/src/testInteg/index.js') -})() diff --git a/packages/core/scripts/test/testWeb.ts b/packages/core/scripts/test/testWeb.ts deleted file mode 100644 index c6eca1469b2..00000000000 --- a/packages/core/scripts/test/testWeb.ts +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { runToolkitTests } from './launchTestUtilities' -void (async () => { - await runToolkitTests('web', 'dist/src/testWeb/testRunnerWebCore.js') -})() diff --git a/packages/core/src/amazonq/commons/baseChatStorage.ts b/packages/core/src/amazonq/commons/baseChatStorage.ts new file mode 100644 index 00000000000..b0a10c8977b --- /dev/null +++ b/packages/core/src/amazonq/commons/baseChatStorage.ts @@ -0,0 +1,38 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import AsyncLock from 'async-lock' + +export abstract class BaseChatSessionStorage { + private lock = new AsyncLock() + protected sessions: Map = new Map() + + abstract createSession(tabID: string): Promise + + public async getSession(tabID: string): Promise { + /** + * The lock here is added in order to mitigate amazon Q's eventing fire & forget design when integrating with mynah-ui that creates a race condition here. + * The race condition happens when handleDevFeatureCommand in src/amazonq/webview/ui/quickActions/handler.ts is firing two events after each other to amazonqFeatureDev controller + * This eventually may make code generation fail as at the moment of that event it may get from the storage a session that has not been properly updated. + */ + return this.lock.acquire(tabID, async () => { + const sessionFromStorage = this.sessions.get(tabID) + if (sessionFromStorage === undefined) { + // If a session doesn't already exist just create it + return this.createSession(tabID) + } + return sessionFromStorage + }) + } + + // Find all sessions that are currently waiting to be authenticated + public getAuthenticatingSessions(): T[] { + return Array.from(this.sessions.values()).filter((session) => session.isAuthenticating) + } + + public deleteSession(tabID: string) { + this.sessions.delete(tabID) + } +} diff --git a/packages/core/src/amazonqFeatureDev/controllers/chat/messenger/messenger.ts b/packages/core/src/amazonq/commons/connector/baseMessenger.ts similarity index 61% rename from packages/core/src/amazonqFeatureDev/controllers/chat/messenger/messenger.ts rename to packages/core/src/amazonq/commons/connector/baseMessenger.ts index a1902bd8472..ab053333432 100644 --- a/packages/core/src/amazonqFeatureDev/controllers/chat/messenger/messenger.ts +++ b/packages/core/src/amazonq/commons/connector/baseMessenger.ts @@ -3,28 +3,37 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { DeletedFileInfo, FollowUpTypes, NewFileInfo } from '../../../types' -import { AuthFollowUpType, AuthMessageDataMap } from '../../../../amazonq/auth/model' +import { ChatItemAction, ProgressField } from '@aws/mynah-ui' +import { AuthFollowUpType, AuthMessageDataMap } from '../../../amazonq/auth/model' +import { FeatureAuthState } from '../../../codewhisperer' +import { i18n } from '../../../shared/i18n-helper' +import { CodeReference } from '../../../amazonq/webview/ui/connector' + +import { MessengerTypes } from '../../../amazonqFeatureDev/controllers/chat/messenger/constants' import { - ChatMessage, + AppToWebViewMessageDispatcher, AsyncEventProgressMessage, - CodeResultMessage, - UpdatePlaceholderMessage, - ChatInputEnabledMessage, AuthenticationUpdateMessage, AuthNeededException, - OpenNewTabMessage, + ChatInputEnabledMessage, + ChatMessage, + CodeResultMessage, FileComponent, -} from '../../../views/connector/connector' -import { AppToWebViewMessageDispatcher } from '../../../views/connector/connector' -import { ChatItemAction } from '@aws/mynah-ui' -import { messageWithConversationId } from '../../../userFacingText' -import { MessengerTypes } from './constants' -import { FeatureAuthState } from '../../../../codewhisperer' -import { CodeReference } from '../../../../codewhispererChat/view/connector/connector' -import { i18n } from '../../../../shared/i18n-helper' + FolderConfirmationMessage, + OpenNewTabMessage, + UpdateAnswerMessage, + UpdatePlaceholderMessage, + UpdatePromptProgressMessage, +} from './connectorMessages' +import { FollowUpTypes } from '../types' +import { messageWithConversationId } from '../../../amazonqFeatureDev/userFacingText' +import { DeletedFileInfo, NewFileInfo } from '../../../amazonqFeatureDev/types' + export class Messenger { - public constructor(private readonly dispatcher: AppToWebViewMessageDispatcher) {} + public constructor( + private readonly dispatcher: AppToWebViewMessageDispatcher, + private readonly sender: string + ) {} public sendAnswer(params: { message?: string @@ -33,6 +42,8 @@ export class Messenger { tabID: string canBeVoted?: boolean snapToTop?: boolean + messageId?: string + disableChatInput?: boolean }) { this.dispatcher.sendChatMessage( new ChatMessage( @@ -43,10 +54,15 @@ export class Messenger { relatedSuggestions: undefined, canBeVoted: params.canBeVoted ?? false, snapToTop: params.snapToTop ?? false, + messageId: params.messageId, }, - params.tabID + params.tabID, + this.sender ) ) + if (params.disableChatInput) { + this.sendChatInputEnabled(params.tabID, false) + } } public sendFeedback(tabID: string) { @@ -73,6 +89,23 @@ export class Messenger { this.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.placeholder.chatInputDisabled')) } + public sendUpdatePromptProgress(tabID: string, progressField: ProgressField | null) { + this.dispatcher.sendUpdatePromptProgress(new UpdatePromptProgressMessage(tabID, this.sender, progressField)) + } + + public sendFolderConfirmationMessage( + tabID: string, + message: string, + folderPath: string, + followUps?: ChatItemAction[] + ) { + this.dispatcher.sendFolderConfirmationMessage( + new FolderConfirmationMessage(tabID, this.sender, message, folderPath, followUps) + ) + + this.sendChatInputEnabled(tabID, false) + } + public sendErrorMessage( errorMessage: string, tabID: string, @@ -85,6 +118,7 @@ export class Messenger { type: 'answer', tabID: tabID, message: showDefaultMessage ? errorMessage : i18n('AWS.amazonq.featureDev.error.technicalDifficulties'), + canBeVoted: true, }) this.sendFeedback(tabID) return @@ -115,35 +149,45 @@ export class Messenger { deletedFiles: DeletedFileInfo[], references: CodeReference[], tabID: string, - uploadId: string + uploadId: string, + codeGenerationId: string ) { - this.dispatcher.sendCodeResult(new CodeResultMessage(filePaths, deletedFiles, references, tabID, uploadId)) + this.dispatcher.sendCodeResult( + new CodeResultMessage(filePaths, deletedFiles, references, tabID, this.sender, uploadId, codeGenerationId) + ) } public sendAsyncEventProgress(tabID: string, inProgress: boolean, message: string | undefined) { - this.dispatcher.sendAsyncEventProgress(new AsyncEventProgressMessage(tabID, inProgress, message)) + this.dispatcher.sendAsyncEventProgress(new AsyncEventProgressMessage(tabID, this.sender, inProgress, message)) } public updateFileComponent( tabID: string, filePaths: NewFileInfo[], deletedFiles: DeletedFileInfo[], - messageId: string + messageId: string, + disableFileActions: boolean ) { - this.dispatcher.updateFileComponent(new FileComponent(tabID, filePaths, deletedFiles, messageId)) + this.dispatcher.updateFileComponent( + new FileComponent(tabID, this.sender, filePaths, deletedFiles, messageId, disableFileActions) + ) + } + + public updateChatAnswer(message: UpdateAnswerMessage) { + this.dispatcher.updateChatAnswer(message) } public sendUpdatePlaceholder(tabID: string, newPlaceholder: string) { - this.dispatcher.sendPlaceholder(new UpdatePlaceholderMessage(tabID, newPlaceholder)) + this.dispatcher.sendPlaceholder(new UpdatePlaceholderMessage(tabID, this.sender, newPlaceholder)) } public sendChatInputEnabled(tabID: string, enabled: boolean) { - this.dispatcher.sendChatInputEnabled(new ChatInputEnabledMessage(tabID, enabled)) + this.dispatcher.sendChatInputEnabled(new ChatInputEnabledMessage(tabID, this.sender, enabled)) } - public sendAuthenticationUpdate(featureDevEnabled: boolean, authenticatingTabIDs: string[]) { + public sendAuthenticationUpdate(enabled: boolean, authenticatingTabIDs: string[]) { this.dispatcher.sendAuthenticationUpdate( - new AuthenticationUpdateMessage(featureDevEnabled, authenticatingTabIDs) + new AuthenticationUpdateMessage(this.sender, enabled, authenticatingTabIDs) ) } @@ -166,10 +210,10 @@ export class Messenger { break } - this.dispatcher.sendAuthNeededExceptionMessage(new AuthNeededException(message, authType, tabID)) + this.dispatcher.sendAuthNeededExceptionMessage(new AuthNeededException(message, authType, tabID, this.sender)) } public openNewTask() { - this.dispatcher.sendOpenNewTask(new OpenNewTabMessage()) + this.dispatcher.sendOpenNewTask(new OpenNewTabMessage(this.sender)) } } diff --git a/packages/core/src/amazonqFeatureDev/views/connector/connector.ts b/packages/core/src/amazonq/commons/connector/connectorMessages.ts similarity index 57% rename from packages/core/src/amazonqFeatureDev/views/connector/connector.ts rename to packages/core/src/amazonq/commons/connector/connectorMessages.ts index 06cba2b6256..f5ac0d4b21a 100644 --- a/packages/core/src/amazonqFeatureDev/views/connector/connector.ts +++ b/packages/core/src/amazonq/commons/connector/connectorMessages.ts @@ -3,20 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { AuthFollowUpType } from '../../../amazonq/auth/model' -import { MessagePublisher } from '../../../amazonq/messages/messagePublisher' -import { CodeReference } from '../../../amazonq/webview/ui/connector' -import { featureDevChat, licenseText } from '../../constants' -import { ChatItemAction, SourceLink } from '@aws/mynah-ui' -import { DeletedFileInfo, NewFileInfo } from '../../types' -import { ChatItemType } from '../../../amazonq/commons/model' +import { AuthFollowUpType } from '../../auth/model' +import { MessagePublisher } from '../../messages/messagePublisher' +import { CodeReference } from '../../webview/ui/connector' +import { ChatItemAction, ProgressField, SourceLink } from '@aws/mynah-ui' +import { ChatItemType } from '../model' +import { DeletedFileInfo, NewFileInfo } from '../../../amazonqFeatureDev/types' +import { licenseText } from '../../../amazonqFeatureDev/constants' class UiMessage { readonly time: number = Date.now() - readonly sender: string = featureDevChat readonly type: string = '' - public constructor(protected tabID: string) {} + public constructor( + protected tabID: string, + protected sender: string + ) {} } export class ErrorMessage extends UiMessage { @@ -24,8 +26,8 @@ export class ErrorMessage extends UiMessage { readonly message!: string override type = 'errorMessage' - constructor(title: string, message: string, tabID: string) { - super(tabID) + constructor(title: string, message: string, tabID: string, sender: string) { + super(tabID, sender) this.title = title this.message = message } @@ -33,6 +35,7 @@ export class ErrorMessage extends UiMessage { export class CodeResultMessage extends UiMessage { readonly message!: string + readonly codeGenerationId!: string readonly references!: { information: string recommendationContentSpan: { @@ -48,9 +51,11 @@ export class CodeResultMessage extends UiMessage { readonly deletedFiles: DeletedFileInfo[], references: CodeReference[], tabID: string, - conversationID: string + sender: string, + conversationID: string, + codeGenerationId: string ) { - super(tabID) + super(tabID, sender) this.references = references .filter((ref) => ref.licenseName && ref.repository && ref.url) .map((ref) => { @@ -64,32 +69,76 @@ export class CodeResultMessage extends UiMessage { }, } }) + this.codeGenerationId = codeGenerationId this.conversationID = conversationID } } +export class FolderConfirmationMessage extends UiMessage { + readonly folderPath: string + readonly message: string + readonly followUps?: ChatItemAction[] + override type = 'folderConfirmationMessage' + constructor(tabID: string, sender: string, message: string, folderPath: string, followUps?: ChatItemAction[]) { + super(tabID, sender) + this.message = message + this.folderPath = folderPath + this.followUps = followUps + } +} + +export class UpdatePromptProgressMessage extends UiMessage { + readonly progressField: ProgressField | null + override type = 'updatePromptProgress' + constructor(tabID: string, sender: string, progressField: ProgressField | null) { + super(tabID, sender) + this.progressField = progressField + } +} + export class AsyncEventProgressMessage extends UiMessage { readonly inProgress: boolean readonly message: string | undefined override type = 'asyncEventProgressMessage' - constructor(tabID: string, inProgress: boolean, message: string | undefined) { - super(tabID) + constructor(tabID: string, sender: string, inProgress: boolean, message: string | undefined) { + super(tabID, sender) this.inProgress = inProgress this.message = message } } + +export class AuthenticationUpdateMessage { + readonly time: number = Date.now() + readonly type = 'authenticationUpdateMessage' + + constructor( + readonly sender: string, + readonly featureEnabled: boolean, + readonly authenticatingTabIDs: string[] + ) {} +} + export class FileComponent extends UiMessage { readonly filePaths: NewFileInfo[] readonly deletedFiles: DeletedFileInfo[] override type = 'updateFileComponent' readonly messageId: string + readonly disableFileActions: boolean - constructor(tabID: string, filePaths: NewFileInfo[], deletedFiles: DeletedFileInfo[], messageId: string) { - super(tabID) + constructor( + tabID: string, + sender: string, + filePaths: NewFileInfo[], + deletedFiles: DeletedFileInfo[], + messageId: string, + disableFileActions: boolean + ) { + super(tabID, sender) this.filePaths = filePaths this.deletedFiles = deletedFiles this.messageId = messageId + this.disableFileActions = disableFileActions } } @@ -97,8 +146,8 @@ export class UpdatePlaceholderMessage extends UiMessage { readonly newPlaceholder: string override type = 'updatePlaceholderMessage' - constructor(tabID: string, newPlaceholder: string) { - super(tabID) + constructor(tabID: string, sender: string, newPlaceholder: string) { + super(tabID, sender) this.newPlaceholder = newPlaceholder } } @@ -107,29 +156,17 @@ export class ChatInputEnabledMessage extends UiMessage { readonly enabled: boolean override type = 'chatInputEnabledMessage' - constructor(tabID: string, enabled: boolean) { - super(tabID) + constructor(tabID: string, sender: string, enabled: boolean) { + super(tabID, sender) this.enabled = enabled } } export class OpenNewTabMessage { readonly time: number = Date.now() - readonly sender: string = featureDevChat readonly type = 'openNewTabMessage' -} -export class AuthenticationUpdateMessage { - readonly time: number = Date.now() - readonly sender: string = featureDevChat - readonly featureDevEnabled: boolean - readonly authenticatingTabIDs: string[] - readonly type = 'authenticationUpdateMessage' - - constructor(featureDevEnabled: boolean, authenticatingTabIDs: string[]) { - this.featureDevEnabled = featureDevEnabled - this.authenticatingTabIDs = authenticatingTabIDs - } + constructor(protected sender: string) {} } export class AuthNeededException extends UiMessage { @@ -137,8 +174,8 @@ export class AuthNeededException extends UiMessage { readonly authType: AuthFollowUpType override type = 'authNeededException' - constructor(message: string, authType: AuthFollowUpType, tabID: string) { - super(tabID) + constructor(message: string, authType: AuthFollowUpType, tabID: string, sender: string) { + super(tabID, sender) this.message = message this.authType = authType } @@ -151,6 +188,7 @@ export interface ChatMessageProps { readonly relatedSuggestions: SourceLink[] | undefined readonly canBeVoted: boolean readonly snapToTop: boolean + readonly messageId?: string } export class ChatMessage extends UiMessage { @@ -161,16 +199,38 @@ export class ChatMessage extends UiMessage { readonly canBeVoted: boolean readonly requestID!: string readonly snapToTop: boolean + readonly messageId: string | undefined override type = 'chatMessage' - constructor(props: ChatMessageProps, tabID: string) { - super(tabID) + constructor(props: ChatMessageProps, tabID: string, sender: string) { + super(tabID, sender) this.message = props.message this.messageType = props.messageType this.followUps = props.followUps this.relatedSuggestions = props.relatedSuggestions this.canBeVoted = props.canBeVoted this.snapToTop = props.snapToTop + this.messageId = props.messageId + } +} + +export interface UpdateAnswerMessageProps { + readonly messageId: string + readonly messageType: ChatItemType + readonly followUps: ChatItemAction[] | undefined +} + +export class UpdateAnswerMessage extends UiMessage { + readonly messageId: string + readonly messageType: ChatItemType + readonly followUps: ChatItemAction[] | undefined + override type = 'updateChatAnswer' + + constructor(props: UpdateAnswerMessageProps, tabID: string, sender: string) { + super(tabID, sender) + this.messageId = props.messageId + this.messageType = props.messageType + this.followUps = props.followUps } } @@ -189,6 +249,14 @@ export class AppToWebViewMessageDispatcher { this.appsToWebViewMessagePublisher.publish(message) } + public sendUpdatePromptProgress(message: UpdatePromptProgressMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendFolderConfirmationMessage(message: FolderConfirmationMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + public sendAsyncEventProgress(message: AsyncEventProgressMessage) { this.appsToWebViewMessagePublisher.publish(message) } @@ -201,11 +269,11 @@ export class AppToWebViewMessageDispatcher { this.appsToWebViewMessagePublisher.publish(message) } - public sendAuthenticationUpdate(message: AuthenticationUpdateMessage) { + public sendAuthNeededExceptionMessage(message: AuthNeededException) { this.appsToWebViewMessagePublisher.publish(message) } - public sendAuthNeededExceptionMessage(message: AuthNeededException) { + public sendAuthenticationUpdate(message: AuthenticationUpdateMessage) { this.appsToWebViewMessagePublisher.publish(message) } @@ -213,7 +281,11 @@ export class AppToWebViewMessageDispatcher { this.appsToWebViewMessagePublisher.publish(message) } - public updateFileComponent(message: any) { + public updateFileComponent(message: FileComponent) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public updateChatAnswer(message: UpdateAnswerMessage) { this.appsToWebViewMessagePublisher.publish(message) } } diff --git a/packages/core/src/amazonq/commons/controllers/contentController.ts b/packages/core/src/amazonq/commons/controllers/contentController.ts index d77ef176ec1..0af3b317025 100644 --- a/packages/core/src/amazonq/commons/controllers/contentController.ts +++ b/packages/core/src/amazonq/commons/controllers/contentController.ts @@ -4,8 +4,29 @@ */ import * as vscode from 'vscode' +import path from 'path' import { Position, TextEditor, window } from 'vscode' import { getLogger } from '../../../shared/logger' +import { amazonQDiffScheme, amazonQTabSuffix } from '../../../shared/constants' +import { disposeOnEditorClose } from '../../../shared/utilities/editorUtilities' +import { + applyChanges, + createTempFileForDiff, + getIndentedCode, + getSelectionFromRange, +} from '../../../shared/utilities/textDocumentUtilities' +import { extractFileAndCodeSelectionFromMessage, fs, getErrorMsg, ToolkitError } from '../../../shared' + +export class ContentProvider implements vscode.TextDocumentContentProvider { + constructor(private uri: vscode.Uri) {} + + provideTextDocumentContent(_uri: vscode.Uri) { + return fs.readFileText(this.uri.fsPath) + } +} + +const chatDiffCode = 'ChatDiff' +const ChatDiffError = ToolkitError.named(chatDiffCode) export class EditorContentController { /* * @@ -52,4 +73,106 @@ export class EditorContentController { ) } } + + /** + * Accepts and applies a diff to a file, then closes the associated diff view tab. + * + * @param {any} message - The message containing diff information. + * @returns {Promise} A promise that resolves when the diff is applied and the tab is closed. + * + * @description + * This method performs the following steps: + * 1. Extracts file path and selection from the message. + * 2. If valid file path, non-empty code, and selection are present: + * a. Opens the document. + * b. Gets the indented code to update. + * c. Applies the changes to the document. + * d. Attempts to close the diff view tab for the file. + * + * @throws {Error} If there's an issue opening the document or applying changes. + */ + public async acceptDiff(message: any) { + const errorNotification = 'Unable to Apply code changes.' + const { filePath, selection } = extractFileAndCodeSelectionFromMessage(message) + + if (filePath && message?.code?.trim().length > 0 && selection) { + try { + const doc = await vscode.workspace.openTextDocument(filePath) + + const code = getIndentedCode(message, doc, selection) + const range = getSelectionFromRange(doc, selection) + await applyChanges(doc, range, code) + + // Sets the editor selection from the start of the given range, extending it by the number of lines in the code till the end of the last line + const editor = await vscode.window.showTextDocument(doc) + editor.selection = new vscode.Selection( + range.start, + new Position(range.start.line + code.split('\n').length, Number.MAX_SAFE_INTEGER) + ) + + // If vscode.diff is open for the filePath then close it. + vscode.window.tabGroups.all.flatMap(({ tabs }) => + tabs.map((tab) => { + if (tab.label === `${path.basename(filePath)} ${amazonQTabSuffix}`) { + const tabClosed = vscode.window.tabGroups.close(tab) + if (!tabClosed) { + getLogger().error( + '%s: Unable to close the diff view tab for %s', + chatDiffCode, + tab.label + ) + } + } + }) + ) + } catch (error) { + void vscode.window.showInformationMessage(errorNotification) + const wrappedError = ChatDiffError.chain(error, `Failed to Accept Diff`, { code: chatDiffCode }) + getLogger().error('%s: Failed to open diff view %s', chatDiffCode, getErrorMsg(wrappedError, true)) + throw wrappedError + } + } + } + + /** + * Displays a diff view comparing proposed changes with the existing file. + * + * How is diff generated: + * 1. Creates a temporary file as a clone of the original file. + * 2. Applies the proposed changes to the temporary file within the selected range. + * 3. Opens a diff view comparing original file to the temporary file. + * + * This approach ensures that the diff view only shows the changes proposed by Amazon Q, + * isolating them from any other modifications in the original file. + * + * @param message the message from Amazon Q chat + */ + public async viewDiff(message: any, scheme: string = amazonQDiffScheme) { + const errorNotification = 'Unable to Open Diff.' + const { filePath, selection } = extractFileAndCodeSelectionFromMessage(message) + + try { + if (filePath && message?.code?.trim().length > 0 && selection) { + const originalFileUri = vscode.Uri.file(filePath) + const uri = await createTempFileForDiff(originalFileUri, message, selection, scheme) + + // Register content provider and show diff + const contentProvider = new ContentProvider(uri) + const disposable = vscode.workspace.registerTextDocumentContentProvider(scheme, contentProvider) + await vscode.commands.executeCommand( + 'vscode.diff', + originalFileUri, + uri, + `${path.basename(filePath)} ${amazonQTabSuffix}` + ) + + disposeOnEditorClose(uri, disposable) + } + } catch (error) { + void vscode.window.showInformationMessage(errorNotification) + const wrappedError = ChatDiffError.chain(error, `Failed to Open Diff View`, { code: chatDiffCode }) + getLogger().error('%s: Failed to open diff view %s', chatDiffCode, getErrorMsg(wrappedError, true)) + throw wrappedError + } + } } diff --git a/packages/core/src/amazonq/commons/diff.ts b/packages/core/src/amazonq/commons/diff.ts index 0e03a234a00..beb45d88096 100644 --- a/packages/core/src/amazonq/commons/diff.ts +++ b/packages/core/src/amazonq/commons/diff.ts @@ -3,32 +3,60 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { existsSync } from 'fs' import * as vscode from 'vscode' -import { featureDevScheme } from '../../amazonqFeatureDev/constants' +import { fs } from '../../shared' +import { diffLines } from 'diff' -export async function openDiff(leftPath: string, rightPath: string, tabId: string) { - const { left, right } = getFileDiffUris(leftPath, rightPath, tabId) +export async function openDiff(leftPath: string, rightPath: string, tabId: string, scheme: string) { + const { left, right } = await getFileDiffUris(leftPath, rightPath, tabId, scheme) await vscode.commands.executeCommand('vscode.diff', left, right) } -export async function openDeletedDiff(filePath: string, name: string, tabId: string) { - const fileUri = getOriginalFileUri(filePath, tabId) - await vscode.commands.executeCommand('vscode.open', fileUri, {}, `${name} (Deleted)`) +export async function openDeletedDiff(filePath: string, name: string, tabId: string, scheme: string) { + const left = await getOriginalFileUri(filePath, tabId, scheme) + const right = createAmazonQUri('empty', tabId, scheme) + await vscode.commands.executeCommand('vscode.diff', left, right, `${name} (Deleted)`) } -export function getOriginalFileUri(fullPath: string, tabId: string) { - return existsSync(fullPath) ? vscode.Uri.file(fullPath) : createAmazonQUri('empty', tabId) +export async function getOriginalFileUri(fullPath: string, tabId: string, scheme: string) { + return (await fs.exists(fullPath)) ? vscode.Uri.file(fullPath) : createAmazonQUri('empty', tabId, scheme) } -export function getFileDiffUris(leftPath: string, rightPath: string, tabId: string) { - const left = getOriginalFileUri(leftPath, tabId) - const right = createAmazonQUri(rightPath, tabId) +export async function getFileDiffUris(leftPath: string, rightPath: string, tabId: string, scheme: string) { + const left = await getOriginalFileUri(leftPath, tabId, scheme) + const right = createAmazonQUri(rightPath, tabId, scheme) return { left, right } } -export function createAmazonQUri(path: string, tabId: string) { - // TODO change the featureDevScheme to a more general amazon q scheme - return vscode.Uri.from({ scheme: featureDevScheme, path, query: `tabID=${tabId}` }) +export function createAmazonQUri(path: string, tabId: string, scheme: string) { + return vscode.Uri.from({ scheme: scheme, path, query: `tabID=${tabId}` }) +} + +export async function computeDiff(leftPath: string, rightPath: string, tabId: string, scheme: string) { + const { left, right } = await getFileDiffUris(leftPath, rightPath, tabId, scheme) + const leftFile = await vscode.workspace.openTextDocument(left) + const rightFile = await vscode.workspace.openTextDocument(right) + + const changes = diffLines(leftFile.getText(), rightFile.getText(), { + ignoreWhitespace: true, + }) + + let charsAdded = 0 + let charsRemoved = 0 + let linesAdded = 0 + let linesRemoved = 0 + changes.forEach((change) => { + const lines = change.value.split('\n') + const charCount = lines.reduce((sum, line) => sum + line.length, 0) + const lineCount = change.count ?? lines.length - 1 // ignoring end-of-file empty line + if (change.added) { + charsAdded += charCount + linesAdded += lineCount + } else if (change.removed) { + charsRemoved += charCount + linesRemoved += lineCount + } + }) + return { changes, charsAdded, linesAdded, charsRemoved, linesRemoved } } diff --git a/packages/core/src/amazonqFeatureDev/session/sessionConfigFactory.ts b/packages/core/src/amazonq/commons/session/sessionConfigFactory.ts similarity index 69% rename from packages/core/src/amazonqFeatureDev/session/sessionConfigFactory.ts rename to packages/core/src/amazonq/commons/session/sessionConfigFactory.ts index 6f98e1b3664..d6dff48fbe5 100644 --- a/packages/core/src/amazonqFeatureDev/session/sessionConfigFactory.ts +++ b/packages/core/src/amazonq/commons/session/sessionConfigFactory.ts @@ -4,11 +4,9 @@ */ import * as vscode from 'vscode' -import { featureDevScheme } from '../constants' -import { VirtualFileSystem } from '../../shared/virtualFilesystem' -import { VirtualMemoryFile } from '../../shared/virtualMemoryFile' -import { WorkspaceFolderNotFoundError } from '../errors' -import { CurrentWsFolders } from '../types' +import { WorkspaceFolderNotFoundError } from '../../../amazonqFeatureDev/errors' +import { VirtualFileSystem, VirtualMemoryFile } from '../../../shared' +import { CurrentWsFolders } from '../../../amazonqFeatureDev/types' export interface SessionConfig { // The paths on disk to where the source code lives @@ -21,7 +19,7 @@ export interface SessionConfig { * Factory method for creating session configurations * @returns An instantiated SessionConfig, using either the arguments provided or the defaults */ -export async function createSessionConfig(): Promise { +export async function createSessionConfig(scheme: string): Promise { const workspaceFolders = vscode.workspace.workspaceFolders const firstFolder = workspaceFolders?.[0] if (workspaceFolders === undefined || workspaceFolders.length === 0 || firstFolder === undefined) { @@ -33,10 +31,7 @@ export async function createSessionConfig(): Promise { const fs = new VirtualFileSystem() // Register an empty featureDev file that's used when a new file is being added by the LLM - fs.registerProvider( - vscode.Uri.from({ scheme: featureDevScheme, path: 'empty' }), - new VirtualMemoryFile(new Uint8Array()) - ) + fs.registerProvider(vscode.Uri.from({ scheme, path: 'empty' }), new VirtualMemoryFile(new Uint8Array())) return Promise.resolve({ workspaceRoots, fs, workspaceFolders: [firstFolder, ...workspaceFolders.slice(1)] }) } diff --git a/packages/core/src/amazonq/commons/types.ts b/packages/core/src/amazonq/commons/types.ts new file mode 100644 index 00000000000..f5724a13872 --- /dev/null +++ b/packages/core/src/amazonq/commons/types.ts @@ -0,0 +1,38 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export enum FollowUpTypes { + // UnitTestGeneration + ViewDiff = 'ViewDiff', + AcceptCode = 'AcceptCode', + RejectCode = 'RejectCode', + BuildAndExecute = 'BuildAndExecute', + ModifyCommands = 'ModifyCommands', + SkipBuildAndFinish = 'SkipBuildAndFinish', + InstallDependenciesAndContinue = 'InstallDependenciesAndContinue', + ContinueBuildAndExecute = 'ContinueBuildAndExecute', + ViewCodeDiffAfterIteration = 'ViewCodeDiffAfterIteration', + // FeatureDev + GenerateCode = 'GenerateCode', + InsertCode = 'InsertCode', + ProvideFeedbackAndRegenerateCode = 'ProvideFeedbackAndRegenerateCode', + Retry = 'Retry', + ModifyDefaultSourceFolder = 'ModifyDefaultSourceFolder', + DevExamples = 'DevExamples', + NewTask = 'NewTask', + CloseSession = 'CloseSession', + SendFeedback = 'SendFeedback', + // Doc + CreateDocumentation = 'CreateDocumentation', + ChooseFolder = 'ChooseFolder', + UpdateDocumentation = 'UpdateDocumentation', + SynchronizeDocumentation = 'SynchronizeDocumentation', + EditDocumentation = 'EditDocumentation', + AcceptChanges = 'AcceptChanges', + RejectChanges = 'RejectChanges', + MakeChanges = 'MakeChanges', + ProceedFolderSelection = 'ProceedFolderSelection', + CancelFolderSelection = 'CancelFolderSelection', +} diff --git a/packages/core/src/amazonq/extApi.ts b/packages/core/src/amazonq/extApi.ts index 8475fc6ba56..874ee018526 100644 --- a/packages/core/src/amazonq/extApi.ts +++ b/packages/core/src/amazonq/extApi.ts @@ -5,6 +5,7 @@ import vscode from 'vscode' import { VSCODE_EXTENSION_ID } from '../shared/utilities' +import { SendMessageCommandOutput, SendMessageRequest } from '@amzn/amazon-q-developer-streaming-client' import { GenerateAssistantResponseCommandOutput, GenerateAssistantResponseRequest } from '@amzn/codewhisperer-streaming' import { FeatureAuthState } from '../codewhisperer/util/authUtil' import { ToolkitError } from '../shared' @@ -16,6 +17,7 @@ import { ToolkitError } from '../shared' export interface api { chatApi: { chat(request: GenerateAssistantResponseRequest): Promise + chatIam(request: SendMessageRequest): Promise } authApi: { reauthIfNeeded(): Promise diff --git a/packages/core/src/amazonq/index.ts b/packages/core/src/amazonq/index.ts index ec7dc9a3bf8..5bd20e4dfd0 100644 --- a/packages/core/src/amazonq/index.ts +++ b/packages/core/src/amazonq/index.ts @@ -22,12 +22,28 @@ export { AmazonQChatViewProvider } from './webview/webView' export { init as cwChatAppInit } from '../codewhispererChat/app' export { init as featureDevChatAppInit } from '../amazonqFeatureDev/app' export { init as gumbyChatAppInit } from '../amazonqGumby/app' +export { init as testChatAppInit } from '../amazonqTest/app' +export { init as docChatAppInit } from '../amazonqDoc/app' export { activateBadge } from './util/viewBadgeHandler' export { amazonQHelpUrl } from '../shared/constants' export { listCodeWhispererCommandsWalkthrough } from '../codewhisperer/ui/statusBarMenu' export { focusAmazonQPanel, focusAmazonQPanelKeybinding } from '../codewhispererChat/commands/registerCommands' export { TryChatCodeLensProvider, tryChatCodeLensCommand } from '../codewhispererChat/editor/codelens' -export { createAmazonQUri, openDiff, openDeletedDiff, getOriginalFileUri, getFileDiffUris } from './commons/diff' +export { + createAmazonQUri, + openDiff, + openDeletedDiff, + getOriginalFileUri, + getFileDiffUris, + computeDiff, +} from './commons/diff' +export { AuthFollowUpType, AuthMessageDataMap } from './auth/model' +export { ChatItemType } from './commons/model' +export { ExtensionMessage } from '../amazonq/webview/ui/commands' +export { CodeReference } from '../codewhispererChat/view/connector/connector' +export { extractAuthFollowUp } from './util/authUtils' +export { Messenger } from './commons/connector/baseMessenger' +import { FeatureContext } from '../shared' /** * main from createMynahUI is a purely browser dependency. Due to this @@ -35,10 +51,17 @@ export { createAmazonQUri, openDiff, openDeletedDiff, getOriginalFileUri, getFil * while only running on browser instances (like the e2e tests). If we * just export it regularly we will get "ReferenceError: self is not defined" */ -export function createMynahUI(ideApi: any, amazonQEnabled: boolean) { +export function createMynahUI( + ideApi: any, + amazonQEnabled: boolean, + featureConfigsSerialized: [string, FeatureContext][], + disabledCommands?: string[] +) { if (typeof window !== 'undefined') { const mynahUI = require('./webview/ui/main') - return mynahUI.createMynahUI(ideApi, amazonQEnabled) + return mynahUI.createMynahUI(ideApi, amazonQEnabled, featureConfigsSerialized, true, disabledCommands) } throw new Error('Not implemented for node') } + +export * from './commons/types' diff --git a/packages/core/src/amazonq/lsp/lspClient.ts b/packages/core/src/amazonq/lsp/lspClient.ts index fc55022bbd0..3969a3313e9 100644 --- a/packages/core/src/amazonq/lsp/lspClient.ts +++ b/packages/core/src/amazonq/lsp/lspClient.ts @@ -10,17 +10,29 @@ import * as vscode from 'vscode' import * as path from 'path' import * as nls from 'vscode-nls' -import * as cp from 'child_process' +import { spawn } from 'child_process' // eslint-disable-line no-restricted-imports import * as crypto from 'crypto' import * as jose from 'jose' import { Disposable, ExtensionContext } from 'vscode' import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient' -import { GetUsageRequestType, IndexRequestType, QueryRequestType, UpdateIndexRequestType, Usage } from './types' +import { + BuildIndexRequestPayload, + BuildIndexRequestType, + GetUsageRequestType, + IndexConfig, + QueryInlineProjectContextRequestType, + QueryVectorIndexRequestType, + UpdateIndexV2RequestPayload, + UpdateIndexV2RequestType, + QueryRepomapIndexRequestType, + GetRepomapIndexJSONRequestType, + Usage, +} from './types' import { Writable } from 'stream' import { CodeWhispererSettings } from '../../codewhisperer/util/codewhispererSettings' -import { fs, getLogger } from '../../shared' +import { fs, getLogger, globals } from '../../shared' const localize = nls.loadMessageBundle() @@ -61,58 +73,99 @@ export class LspClient { .encrypt(key) } - async indexFiles(request: string[], rootPath: string, refresh: boolean) { + async buildIndex(paths: string[], rootPath: string, config: IndexConfig) { + const payload: BuildIndexRequestPayload = { + filePaths: paths, + projectRoot: rootPath, + config: config, + language: '', + } try { - const encryptedRequest = await this.encrypt( - JSON.stringify({ - filePaths: request, - rootPath: rootPath, - refresh: refresh, - }) - ) - const resp = await this.client?.sendRequest(IndexRequestType, encryptedRequest) + const encryptedRequest = await this.encrypt(JSON.stringify(payload)) + const resp = await this.client?.sendRequest(BuildIndexRequestType, encryptedRequest) return resp } catch (e) { - getLogger().error(`LspClient: indexFiles error: ${e}`) + getLogger().error(`LspClient: buildIndex error: ${e}`) return undefined } } - async query(request: string) { + async queryVectorIndex(request: string) { try { const encryptedRequest = await this.encrypt( JSON.stringify({ query: request, }) ) - const resp = await this.client?.sendRequest(QueryRequestType, encryptedRequest) + const resp = await this.client?.sendRequest(QueryVectorIndexRequestType, encryptedRequest) return resp } catch (e) { - getLogger().error(`LspClient: query error: ${e}`) + getLogger().error(`LspClient: queryVectorIndex error: ${e}`) return [] } } + async queryInlineProjectContext(query: string, path: string, target: 'default' | 'codemap' | 'bm25') { + try { + const request = JSON.stringify({ + query: query, + filePath: path, + target, + }) + const encrypted = await this.encrypt(request) + const resp: any = await this.client?.sendRequest(QueryInlineProjectContextRequestType, encrypted) + return resp + } catch (e) { + getLogger().error(`LspClient: queryInlineProjectContext error: ${e}`) + throw e + } + } + async getLspServerUsage(): Promise { if (this.client) { return (await this.client.sendRequest(GetUsageRequestType, '')) as Usage } } - async updateIndex(filePath: string) { + async updateIndex(filePath: string[], mode: 'update' | 'remove' | 'add') { + const payload: UpdateIndexV2RequestPayload = { + filePaths: filePath, + updateMode: mode, + } try { - const encryptedRequest = await this.encrypt( - JSON.stringify({ - filePath: filePath, - }) - ) - const resp = await this.client?.sendRequest(UpdateIndexRequestType, encryptedRequest) + const encryptedRequest = await this.encrypt(JSON.stringify(payload)) + const resp = await this.client?.sendRequest(UpdateIndexV2RequestType, encryptedRequest) return resp } catch (e) { getLogger().error(`LspClient: updateIndex error: ${e}`) return undefined } } + async queryRepomapIndex(filePaths: string[]) { + try { + const request = JSON.stringify({ + filePaths: filePaths, + }) + const resp: any = await this.client?.sendRequest(QueryRepomapIndexRequestType, await this.encrypt(request)) + return resp + } catch (e) { + getLogger().error(`LspClient: QueryRepomapIndex error: ${e}`) + throw e + } + } + async getRepoMapJSON() { + try { + const request = JSON.stringify({}) + const resp: any = await this.client?.sendRequest( + GetRepomapIndexJSONRequestType, + await this.encrypt(request) + ) + return resp + } catch (e) { + getLogger().error(`LspClient: queryInlineProjectContext error: ${e}`) + throw e + } + } } /** * Activates the language server, this will start LSP server running over IPC protocol. @@ -146,7 +199,7 @@ export async function activate(extensionContext: ExtensionContext) { const nodename = process.platform === 'win32' ? 'node.exe' : 'node' - const child = cp.spawn(extensionContext.asAbsolutePath(path.join('resources', nodename)), [ + const child = spawn(extensionContext.asAbsolutePath(path.join('resources', nodename)), [ serverModule, ...debugOptions.execArgv, ]) @@ -175,6 +228,9 @@ export async function activate(extensionContext: ExtensionContext) { // this is used by LSP to determine index cache path, move to this folder so that when extension updates index is not deleted. extensionPath: path.join(fs.getUserHomeDir(), '.aws', 'amazonq', 'cache'), }, + // Log to the Amazon Q Logs so everything is in a single channel + // TODO: Add prefix to the language server logs so it is easier to search + outputChannel: globals.logOutputChannel, } // Create the language client and start the client. @@ -197,15 +253,26 @@ export async function activate(extensionContext: ExtensionContext) { return } savedDocument = document.uri - }) - ) - toDispose.push( + }), vscode.window.onDidChangeActiveTextEditor((editor) => { if (savedDocument && editor && editor.document.uri.fsPath !== savedDocument.fsPath) { - void LspClient.instance.updateIndex(savedDocument.fsPath) + void LspClient.instance.updateIndex([savedDocument.fsPath], 'update') } + }), + vscode.workspace.onDidCreateFiles((e) => { + void LspClient.instance.updateIndex( + e.files.map((f) => f.fsPath), + 'add' + ) + }), + vscode.workspace.onDidDeleteFiles((e) => { + void LspClient.instance.updateIndex( + e.files.map((f) => f.fsPath), + 'remove' + ) }) ) + return LspClient.instance.client.onReady().then(() => { const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void } toDispose.push(disposableFunc) diff --git a/packages/core/src/amazonq/lsp/lspController.ts b/packages/core/src/amazonq/lsp/lspController.ts index 966eee8190e..bb5cc88f5a2 100644 --- a/packages/core/src/amazonq/lsp/lspController.ts +++ b/packages/core/src/amazonq/lsp/lspController.ts @@ -5,8 +5,8 @@ import * as vscode from 'vscode' import * as path from 'path' -import * as fs from 'fs-extra' import * as crypto from 'crypto' +import { createWriteStream } from 'fs' // eslint-disable-line no-restricted-imports import { getLogger } from '../../shared/logger/logger' import { CurrentWsFolders, collectFilesForIndex } from '../../shared/utilities/workspaceUtils' import fetch from 'node-fetch' @@ -15,24 +15,14 @@ import { LspClient } from './lspClient' import AdmZip from 'adm-zip' import { RelevantTextDocument } from '@amzn/codewhisperer-streaming' import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities' -import { CodeWhispererSettings } from '../../codewhisperer/util/codewhispererSettings' import { activate as activateLsp } from './lspClient' import { telemetry } from '../../shared/telemetry' import { isCloud9 } from '../../shared/extensionUtilities' -import { globals, ToolkitError } from '../../shared' -import { AuthUtil } from '../../codewhisperer' +import { fs, globals, ToolkitError } from '../../shared' import { isWeb } from '../../shared/extensionGlobals' import { getUserAgent } from '../../shared/telemetry/util' import { isAmazonInternalOs } from '../../shared/vscode/env' -function getProjectPaths() { - const workspaceFolders = vscode.workspace.workspaceFolders - if (!workspaceFolders || workspaceFolders.length === 0) { - throw new ToolkitError('No workspace folders found') - } - return workspaceFolders.map((folder) => folder.uri.fsPath) -} - export interface Chunk { readonly filePath: string readonly content: string @@ -68,9 +58,16 @@ export interface Manifest { } const manifestUrl = 'https://aws-toolkit-language-servers.amazonaws.com/q-context/manifest.json' // this LSP client in Q extension is only going to work with these LSP server versions -const supportedLspServerVersions = ['0.1.9'] +const supportedLspServerVersions = ['0.1.32'] const nodeBinName = process.platform === 'win32' ? 'node.exe' : 'node' + +export interface BuildIndexConfig { + startUrl?: string + maxIndexSize: number + isVectorIndexEnabled: boolean +} + /* * LSP Controller manages the status of Amazon Q LSP: * 1. Downloading, verifying and installing LSP using DEXP LSP manifest and CDN. @@ -105,7 +102,7 @@ export class LspController { throw new ToolkitError(`Failed to download. Error: ${JSON.stringify(res)}`) } return new Promise((resolve, reject) => { - const file = fs.createWriteStream(localFile) + const file = createWriteStream(localFile) res.body.pipe(file) res.body.on('error', (err) => { reject(err) @@ -133,16 +130,16 @@ export class LspController { } async getFileSha384(filePath: string): Promise { - const fileBuffer = await fs.promises.readFile(filePath) + const fileBuffer = await fs.readFileBytes(filePath) const hash = crypto.createHash('sha384') hash.update(fileBuffer) return hash.digest('hex') } - isLspInstalled(context: vscode.ExtensionContext) { + async isLspInstalled(context: vscode.ExtensionContext) { const localQServer = context.asAbsolutePath(path.join('resources', 'qserver')) const localNodeRuntime = context.asAbsolutePath(path.join('resources', nodeBinName)) - return fs.existsSync(localQServer) && fs.existsSync(localNodeRuntime) + return (await fs.exists(localQServer)) && (await fs.exists(localNodeRuntime)) } getQserverFromManifest(manifest: Manifest): Content | undefined { @@ -207,7 +204,7 @@ export class LspController { getLogger().error( `LspController: Downloaded file sha ${sha384} does not match manifest ${content.hashes[0]}.` ) - fs.removeSync(filePath) + await fs.delete(filePath) return false } return true @@ -225,19 +222,19 @@ export class LspController { async tryInstallLsp(context: vscode.ExtensionContext): Promise { let tempFolder = undefined try { - if (this.isLspInstalled(context)) { + if (await this.isLspInstalled(context)) { getLogger().info(`LspController: LSP already installed`) return true } // clean up previous downloaded LSP const qserverPath = context.asAbsolutePath(path.join('resources', 'qserver')) - if (fs.existsSync(qserverPath)) { + if (await fs.exists(qserverPath)) { await tryRemoveFolder(qserverPath) } // clean up previous downloaded node runtime const nodeRuntimePath = context.asAbsolutePath(path.join('resources', nodeBinName)) - if (fs.existsSync(nodeRuntimePath)) { - fs.rmSync(nodeRuntimePath) + if (await fs.exists(nodeRuntimePath)) { + await fs.delete(nodeRuntimePath) } // fetch download url for qserver and node runtime const manifest: Manifest = (await this.fetchManifest()) as Manifest @@ -258,7 +255,7 @@ export class LspController { } const zip = new AdmZip(qserverZipTempPath) zip.extractAllTo(tempFolder) - fs.moveSync(path.join(tempFolder, 'qserver'), qserverPath) + await fs.rename(path.join(tempFolder, 'qserver'), qserverPath) // download node runtime to temp folder const nodeRuntimeTempPath = path.join(tempFolder, nodeBinName) @@ -266,8 +263,8 @@ export class LspController { if (!downloadNodeOk) { return false } - fs.chmodSync(nodeRuntimeTempPath, 0o755) - fs.moveSync(nodeRuntimeTempPath, nodeRuntimePath) + await fs.chmod(nodeRuntimeTempPath, 0o755) + await fs.rename(nodeRuntimeTempPath, nodeRuntimePath) return true } catch (e) { getLogger().error(`LspController: Failed to setup LSP server ${e}`) @@ -281,11 +278,11 @@ export class LspController { } async query(s: string): Promise { - const chunks: Chunk[] | undefined = await LspClient.instance.query(s) + const chunks: Chunk[] | undefined = await LspClient.instance.queryVectorIndex(s) const resp: RelevantTextDocument[] = [] chunks?.forEach((chunk) => { const text = chunk.context ? chunk.context : chunk.content - if (chunk.programmingLanguage) { + if (chunk.programmingLanguage && chunk.programmingLanguage !== 'unknown') { resp.push({ text: text, relativeFilePath: chunk.relativePath ? chunk.relativePath : path.basename(chunk.filePath), @@ -303,35 +300,45 @@ export class LspController { return resp } - async buildIndex() { - getLogger().info(`LspController: Starting to build vector index of project`) + async queryInlineProjectContext(query: string, path: string, target: 'bm25' | 'codemap' | 'default') { + try { + return await LspClient.instance.queryInlineProjectContext(query, path, target) + } catch (e) { + if (e instanceof Error) { + getLogger().error(`unexpected error while querying inline project context, e=${e.message}`) + } + return [] + } + } + + async buildIndex(buildIndexConfig: BuildIndexConfig) { + getLogger().info(`LspController: Starting to build index of project`) const start = performance.now() - const projPaths = getProjectPaths() + const projPaths = (vscode.workspace.workspaceFolders ?? []).map((folder) => folder.uri.fsPath) + if (projPaths.length === 0) { + getLogger().info(`LspController: Skipping building index. No projects found in workspace`) + return + } projPaths.sort() try { - if (projPaths.length === 0) { - throw Error('No project') - } this._isIndexingInProgress = true const projRoot = projPaths[0] const files = await collectFilesForIndex( projPaths, vscode.workspace.workspaceFolders as CurrentWsFolders, true, - CodeWhispererSettings.instance.getMaxIndexSize() * 1024 * 1024 + buildIndexConfig.maxIndexSize * 1024 * 1024 ) const totalSizeBytes = files.reduce( (accumulator, currentFile) => accumulator + currentFile.fileSizeBytes, 0 ) - getLogger().info(`LspController: Found ${files.length} files in current project ${getProjectPaths()}`) - const resp = await LspClient.instance.indexFiles( - files.map((f) => f.fileUri.fsPath), - projRoot, - false - ) + getLogger().info(`LspController: Found ${files.length} files in current project ${projPaths}`) + const config = buildIndexConfig.isVectorIndexEnabled ? 'all' : 'default' + const r = files.map((f) => f.fileUri.fsPath) + const resp = await LspClient.instance.buildIndex(r, projRoot, config) if (resp) { - getLogger().debug(`LspController: Finish building vector index of project`) + getLogger().debug(`LspController: Finish building index of project`) const usage = await LspClient.instance.getLspServerUsage() telemetry.amazonq_indexWorkspace.emit({ duration: performance.now() - start, @@ -340,43 +347,41 @@ export class LspController { amazonqIndexMemoryUsageInMB: usage ? usage.memoryUsage / (1024 * 1024) : undefined, amazonqIndexCpuUsagePercentage: usage ? usage.cpuUsage : undefined, amazonqIndexFileSizeInMB: totalSizeBytes / (1024 * 1024), - credentialStartUrl: AuthUtil.instance.startUrl, + credentialStartUrl: buildIndexConfig.startUrl, }) } else { - getLogger().error(`LspController: Failed to build vector index of project`) + getLogger().error(`LspController: Failed to build index of project`) telemetry.amazonq_indexWorkspace.emit({ duration: performance.now() - start, result: 'Failed', amazonqIndexFileCount: 0, amazonqIndexFileSizeInMB: 0, + reason: `Unknown`, }) } - } catch (e) { - getLogger().error(`LspController: Failed to build vector index of project`) + } catch (error) { + // TODO: use telemetry.run() + getLogger().error(`LspController: Failed to build index of project`) telemetry.amazonq_indexWorkspace.emit({ duration: performance.now() - start, result: 'Failed', amazonqIndexFileCount: 0, amazonqIndexFileSizeInMB: 0, + reason: `${error instanceof Error ? error.name : 'Unknown'}`, + reasonDesc: `Error when building index. ${error instanceof Error ? error.message : error}`, }) } finally { this._isIndexingInProgress = false } } - async trySetupLsp(context: vscode.ExtensionContext) { + async trySetupLsp(context: vscode.ExtensionContext, buildIndexConfig: BuildIndexConfig) { if (isCloud9() || isWeb() || isAmazonInternalOs()) { getLogger().warn('LspController: Skipping LSP setup. LSP is not compatible with the current environment. ') // do not do anything if in Cloud9 or Web mode or in AL2 (AL2 does not support node v18+) return } setImmediate(async () => { - if (!CodeWhispererSettings.instance.isLocalIndexEnabled()) { - // only download LSP for users who did not turn on this feature - // do not start LSP server - await LspController.instance.tryInstallLsp(context) - return - } const ok = await LspController.instance.tryInstallLsp(context) if (!ok) { return @@ -384,7 +389,7 @@ export class LspController { try { await activateLsp(context) getLogger().info('LspController: LSP activated') - void LspController.instance.buildIndex() + void LspController.instance.buildIndex(buildIndexConfig) // log the LSP server CPU and Memory usage per 30 minutes. globals.clock.setInterval( async () => { diff --git a/packages/core/src/amazonq/lsp/types.ts b/packages/core/src/amazonq/lsp/types.ts index bc6e37a75d9..3af943cb97d 100644 --- a/packages/core/src/amazonq/lsp/types.ts +++ b/packages/core/src/amazonq/lsp/types.ts @@ -11,10 +11,6 @@ export type IndexRequestPayload = { refresh: boolean } -export type IndexRequest = string - -export const IndexRequestType: RequestType = new RequestType('lsp/index') - export type ClearRequest = string export const ClearRequestType: RequestType = new RequestType('lsp/clear') @@ -23,10 +19,6 @@ export type QueryRequest = string export const QueryRequestType: RequestType = new RequestType('lsp/query') -export type UpdateIndexRequest = string - -export const UpdateIndexRequestType: RequestType = new RequestType('lsp/updateIndex') - export type GetUsageRequest = string export const GetUsageRequestType: RequestType = new RequestType('lsp/getUsage') @@ -35,3 +27,52 @@ export interface Usage { memoryUsage: number cpuUsage: number } + +export type BuildIndexRequestPayload = { + filePaths: string[] + projectRoot: string + config: string + language: string +} + +export type BuildIndexRequest = string + +export const BuildIndexRequestType: RequestType = new RequestType('lsp/buildIndex') + +export type UpdateIndexV2Request = string + +export type UpdateIndexV2RequestPayload = { filePaths: string[]; updateMode: string } + +export const UpdateIndexV2RequestType: RequestType = new RequestType( + 'lsp/updateIndexV2' +) + +export type QueryInlineProjectContextRequest = string +export type QueryInlineProjectContextRequestPayload = { + query: string + filePath: string + target: string +} +export const QueryInlineProjectContextRequestType: RequestType = + new RequestType('lsp/queryInlineProjectContext') + +export type QueryVectorIndexRequestPayload = { query: string } + +export type QueryVectorIndexRequest = string + +export const QueryVectorIndexRequestType: RequestType = new RequestType( + 'lsp/queryVectorIndex' +) + +export type IndexConfig = 'all' | 'default' + +// RepoMapData +export type QueryRepomapIndexRequestPayload = { filePaths: string[] } +export type QueryRepomapIndexRequest = string +export const QueryRepomapIndexRequestType: RequestType = new RequestType( + 'lsp/queryRepomapIndex' +) +export type GetRepomapIndexJSONRequest = string +export const GetRepomapIndexJSONRequestType: RequestType = new RequestType( + 'lsp/getRepomapIndexJSON' +) diff --git a/packages/core/src/amazonq/messages/chatMessageDuration.ts b/packages/core/src/amazonq/messages/chatMessageDuration.ts index 345aa1bd589..d4b087f64df 100644 --- a/packages/core/src/amazonq/messages/chatMessageDuration.ts +++ b/packages/core/src/amazonq/messages/chatMessageDuration.ts @@ -3,91 +3,128 @@ * SPDX-License-Identifier: Apache-2.0 */ +import AsyncLock from 'async-lock' import { globals } from '../../shared' import { telemetry } from '../../shared/telemetry' import { Event, uiEventRecorder } from '../util/eventRecorder' +import { CWCTelemetryHelper } from '../../codewhispererChat/controllers/chat/telemetryHelper' +import { TabType } from '../webview/ui/storages/tabsStorage' export class AmazonQChatMessageDuration { + private static _asyncLock = new AsyncLock() + private static getAsyncLock() { + if (!AmazonQChatMessageDuration._asyncLock) { + AmazonQChatMessageDuration._asyncLock = new AsyncLock() + } + return AmazonQChatMessageDuration._asyncLock + } + /** * Record the initial requests in the chat message flow */ - static startChatMessageTelemetry(msg: { traceId: string; startTime: number; trigger?: string }) { - const { traceId, startTime, trigger } = msg + static startChatMessageTelemetry(msg: { traceId: string; startTime: number; tabID: string; trigger?: string }) { + const { traceId, startTime, tabID, trigger } = msg - uiEventRecorder.set(traceId, { + uiEventRecorder.set(tabID, { + traceId, events: { chatMessageSent: startTime, - }, - }) - uiEventRecorder.set(traceId, { - events: { editorReceivedMessage: globals.clock.Date.now(), }, }) if (trigger) { - uiEventRecorder.set(traceId, { + uiEventRecorder.set(tabID, { trigger, }) } + CWCTelemetryHelper.instance.setDisplayTimeForChunks(tabID, startTime) } /** * Stop listening to all incoming events and emit what we've found */ - static stopChatMessageTelemetry(msg: { traceId: string }) { - const { traceId } = msg + static stopChatMessageTelemetry(msg: { tabID: string; time: number; tabType: TabType }) { + const { tabID, time, tabType } = msg // We can't figure out what trace this event was associated with - if (!traceId) { + if (!tabID || tabType !== 'cwc') { return } - uiEventRecorder.set(traceId, { - events: { - messageDisplayed: globals.clock.Date.now(), - }, - }) + // Lock the tab id just in case another event tries to trigger this + void AmazonQChatMessageDuration.getAsyncLock().acquire(tabID, () => { + const metrics = uiEventRecorder.get(tabID) + if (!metrics) { + return + } - const metrics = uiEventRecorder.get(traceId) - - // get events sorted by the time they were created - const events = Object.entries(metrics.events) - .map((x) => ({ - event: x[0], - duration: x[1], - })) - .sort((a, b) => { - return a.duration - b.duration + uiEventRecorder.set(tabID, { + events: { + messageDisplayed: time, + }, }) - const chatMessageSentTime = events[events.length - 1].duration - // Get the total duration by subtracting when the message was displayed and when the chat message was first sent - const totalDuration = events[events.length - 1].duration - events[0].duration + const displayTime = metrics.events.messageDisplayed + const sentTime = metrics.events.chatMessageSent + if (!displayTime || !sentTime) { + return + } - /** - * Find the time it took to get between two metric events - */ - const timings = new Map() - for (let i = 1; i < events.length; i++) { - const currentEvent = events[i] - const previousEvent = events[i - 1] + const totalDuration = displayTime - sentTime - const timeDifference = currentEvent.duration - previousEvent.duration + function durationFrom(start: Event, end: Event) { + const startEvent = metrics.events[start] + const endEvent = metrics.events[end] + if (!startEvent || !endEvent) { + return -1 + } + return endEvent - startEvent + } - timings.set(currentEvent.event as Event, timeDifference) - } + // TODO: handle onContextCommand round trip time + if (metrics.trigger !== 'onContextCommand') { + const editorReceivedMessage = durationFrom('chatMessageSent', 'editorReceivedMessage') + const featureReceivedMessage = durationFrom('editorReceivedMessage', 'featureReceivedMessage') + const messageDisplayed = durationFrom('featureReceivedMessage', 'messageDisplayed') + let reasonDesc = undefined - telemetry.amazonq_chatRoundTrip.emit({ - amazonqChatMessageSentTime: chatMessageSentTime, - amazonqEditorReceivedMessageMs: timings.get('editorReceivedMessage') ?? -1, - amazonqFeatureReceivedMessageMs: timings.get('featureReceivedMessage') ?? -1, - amazonqMessageDisplayedMs: timings.get('messageDisplayed') ?? -1, - source: metrics.trigger, - duration: totalDuration, - result: 'Succeeded', - traceId, + /** + * Temporary include more information about outliers so that we can find out if the messages + * aren't being sent or the user is actually doing a different chat flow + */ + if ([editorReceivedMessage, featureReceivedMessage].some((val) => val > 30000 || val < -30000)) { + reasonDesc = JSON.stringify(metrics.events) + } + telemetry.amazonq_chatRoundTrip.emit({ + amazonqChatMessageSentTime: metrics.events.chatMessageSent ?? -1, + amazonqEditorReceivedMessageMs: editorReceivedMessage ?? -1, + amazonqFeatureReceivedMessageMs: featureReceivedMessage ?? -1, + amazonqMessageDisplayedMs: messageDisplayed ?? -1, + source: metrics.trigger, + duration: totalDuration, + result: 'Succeeded', + traceId: metrics.traceId, + ...(reasonDesc !== undefined ? { reasonDesc } : {}), + }) + } + + CWCTelemetryHelper.instance.emitAddMessage( + tabID, + totalDuration, + metrics.traceId, + metrics.events.chatMessageSent + ) + + uiEventRecorder.delete(tabID) }) + } + + static updateChatMessageTelemetry(msg: { tabID: string; time: number; tabType: TabType }) { + const { tabID, time, tabType } = msg + if (!tabID || tabType !== 'cwc') { + return + } - uiEventRecorder.delete(traceId) + CWCTelemetryHelper.instance.setDisplayTimeForChunks(tabID, time) } } diff --git a/packages/core/src/amazonq/onboardingPage/walkthrough.ts b/packages/core/src/amazonq/onboardingPage/walkthrough.ts index 63c5db3a87f..30e31ac1055 100644 --- a/packages/core/src/amazonq/onboardingPage/walkthrough.ts +++ b/packages/core/src/amazonq/onboardingPage/walkthrough.ts @@ -34,7 +34,7 @@ export async function showAmazonQWalkthroughOnce(showWalkthrough = () => openAma * Opens the Amazon Q Walkthrough. * We wrap the actual command so that we can get telemetry from it. */ -export const openAmazonQWalkthrough = Commands.declare(`_aws.amazonq.walkthrough.show`, () => async () => { +export const openAmazonQWalkthrough = Commands.declare(`aws.amazonq.walkthrough.show`, () => async () => { await vscode.commands.executeCommand( 'workbench.action.openWalkthrough', `${VSCODE_EXTENSION_ID.amazonq}#aws.amazonq.walkthrough` @@ -69,7 +69,7 @@ fake_users = [ export const walkthroughSecurityScanExample = Commands.declare( `_aws.amazonq.walkthrough.securityScanExample`, () => async () => { - const filterText = localize('AWS.command.amazonq.security.scan', 'Run Project Scan') + const filterText = localize('AWS.command.amazonq.security.scan', 'Run Project Review') void vscode.commands.executeCommand('workbench.action.quickOpen', `> ${filterText}`) } ) diff --git a/packages/core/src/amazonq/util/authUtils.ts b/packages/core/src/amazonq/util/authUtils.ts new file mode 100644 index 00000000000..0fd48ebc4c9 --- /dev/null +++ b/packages/core/src/amazonq/util/authUtils.ts @@ -0,0 +1,40 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { FeatureAuthState } from '../../codewhisperer' +import { AuthFollowUpType, AuthMessageDataMap } from '../auth/model' + +/** + * This function evaluates the authentication state of CodeWhisperer features (chat and core) + * when the authentication is not valid, and returns the appropriate authentication follow-up type and message. + * + * @param credentialState - The current authentication state for each CodeWhisperer feature + * @returns An object containing: + * - authType: The type of authentication follow-up required (AuthFollowUpType) + * - message: The corresponding message for the determined auth type + */ +export function extractAuthFollowUp(credentialState: FeatureAuthState) { + let authType: AuthFollowUpType = 'full-auth' + let message = AuthMessageDataMap[authType].message + if (credentialState.codewhispererChat === 'disconnected' && credentialState.codewhispererCore === 'disconnected') { + authType = 'full-auth' + message = AuthMessageDataMap[authType].message + } + + if (credentialState.codewhispererCore === 'connected' && credentialState.codewhispererChat === 'expired') { + authType = 'missing_scopes' + message = AuthMessageDataMap[authType].message + } + + if (credentialState.codewhispererChat === 'expired' && credentialState.codewhispererCore === 'expired') { + authType = 're-auth' + message = AuthMessageDataMap[authType].message + } + + return { + authType, + message, + } as const +} diff --git a/packages/core/src/amazonq/util/eventRecorder.ts b/packages/core/src/amazonq/util/eventRecorder.ts index 05eccd27fa1..de7f84b7fba 100644 --- a/packages/core/src/amazonq/util/eventRecorder.ts +++ b/packages/core/src/amazonq/util/eventRecorder.ts @@ -9,10 +9,10 @@ export type Event = | 'chatMessageSent' // initial on chat prompt event in the ui | 'editorReceivedMessage' // message gets from the chat prompt to VSCode | 'featureReceivedMessage' // message gets redirected from VSCode -> Partner team features implementation - | 'messageDisplayed' // message gets received in the UI + | 'messageDisplayed' // message gets shown in the UI /** - * For a given traceID, map an event to a time + * For a given tabId, map an event to a time * * This is used to correlated disjoint events that are happening in different * parts of Q Chat. @@ -22,8 +22,14 @@ export type Event = * - when the feature starts processing the message * - final message rendering * and emit those as a final result, rather than having to emit each event individually + * + * Event timings are generated using Date.now() instead of performance.now() for cross-context consistency. + * performance.now() provides timestamps relative to the context's time origin (when the webview or VS Code was opened), + * which can lead to inconsistent measurements between the webview and vscode. + * Date.now() is more consistent across both contexts */ export const uiEventRecorder = new RecordMap<{ trigger: string + traceId: string events: Partial> }>() diff --git a/packages/core/src/amazonq/util/functionUtils.ts b/packages/core/src/amazonq/util/functionUtils.ts new file mode 100644 index 00000000000..b5d6a9bb9dc --- /dev/null +++ b/packages/core/src/amazonq/util/functionUtils.ts @@ -0,0 +1,24 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Tries to create map and returns empty map if failed. + * + * @param {[unknown, unknown][]} arr - An array of tuples, where each tuple represents a key-value pair. + * @returns {Map} A new Map object created from the input array. + * If the conversion fails, an empty Map is returned. + * + * @example + * const array = [['key1', 'value1'], ['key2', 'value2']]; + * const map = tryNewMap(array); + * // map is now a Map object with entries: { 'key1' => 'value1', 'key2' => 'value2' } + */ +export function tryNewMap(arr: [unknown, unknown][]) { + try { + return new Map(arr) + } catch (error) { + return new Map() + } +} diff --git a/packages/core/src/amazonq/webview/generators/webViewContent.ts b/packages/core/src/amazonq/webview/generators/webViewContent.ts index 47cb59a9f2e..ea577574d4c 100644 --- a/packages/core/src/amazonq/webview/generators/webViewContent.ts +++ b/packages/core/src/amazonq/webview/generators/webViewContent.ts @@ -6,9 +6,23 @@ import path from 'path' import { Uri, Webview } from 'vscode' import { AuthUtil } from '../../../codewhisperer/util/authUtil' -import { globals } from '../../../shared' +import { FeatureConfigProvider, FeatureContext, globals, isSageMaker } from '../../../shared' export class WebViewContentGenerator { + private async generateFeatureConfigsData(): Promise { + let featureConfigs = new Map() + try { + await FeatureConfigProvider.instance.fetchFeatureConfigs() + featureConfigs = FeatureConfigProvider.getFeatureConfigs() + } catch (error) { + // eslint-disable-next-line aws-toolkits/no-console-log + console.error('Error fetching feature configs:', error) + } + + // Convert featureConfigs to a string suitable for data-features + return JSON.stringify(Array.from(featureConfigs.entries())) + } + public async generate(extensionURI: Uri, webView: Webview): Promise { const entrypoint = process.env.WEBPACK_DEVELOPER_SERVER ? 'http: localhost' @@ -17,14 +31,25 @@ export class WebViewContentGenerator { const contentPolicy = `default-src ${entrypoint} data: blob: 'unsafe-inline'; script-src ${entrypoint} filesystem: ws: wss: 'unsafe-inline';` + let featureDataAttributes = '' + try { + // Fetch and parse featureConfigs + const featureConfigs = JSON.parse(await this.generateFeatureConfigsData()) + featureDataAttributes = featureConfigs + .map((config: FeatureContext[]) => `data-feature-${config[1].name}="${config[1].variation}"`) + .join(' ') + } catch (error) { + // eslint-disable-next-line aws-toolkits/no-console-log + console.error('Error setting data-feature attribute for featureConfigs:', error) + } return ` - Amazon Q (Preview) - ${await this.generateJS(extensionURI, webView)} + Amazon Q (Preview) + ${await this.generateJS(extensionURI, webView)} - + ` } @@ -41,20 +66,32 @@ export class WebViewContentGenerator { ? Uri.parse(serverHostname).with({ path: `/${source}` }) : webView.asWebviewUri(javascriptUri) - const cssEntrypoint = webView.asWebviewUri( - Uri.joinPath(globals.context.extensionUri, 'resources', 'css', 'amazonq-webview.css') - ) + const cssEntrypoints = [ + Uri.joinPath(globals.context.extensionUri, 'resources', 'css', 'amazonq-webview.css'), + Uri.joinPath(globals.context.extensionUri, 'resources', 'css', 'amazonq-chat.css'), + ] + + const cssEntrypointsMap = cssEntrypoints.map((item) => webView.asWebviewUri(item)) + const cssLinks = cssEntrypointsMap.map((uri) => ``).join('\n') + + // Fetch featureConfigs and use it within the script + const featureConfigsString = await this.generateFeatureConfigsData() + + const disabledCommandsString = isSageMaker() ? `['/dev', '/transform']` : '[]' + const disclaimerAcknowledged = globals.globalState.tryGet('aws.amazonq.disclaimerAcknowledged', Boolean, false) + + const welcomeLoadCount = globals.globalState.tryGet('aws.amazonq.welcomeChatShowCount', Number, 0) return ` - + ${cssLinks} + ` } } diff --git a/packages/core/src/amazonq/webview/messages/messageDispatcher.ts b/packages/core/src/amazonq/webview/messages/messageDispatcher.ts index 8bc12c1bed7..6acc250a25f 100644 --- a/packages/core/src/amazonq/webview/messages/messageDispatcher.ts +++ b/packages/core/src/amazonq/webview/messages/messageDispatcher.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Webview } from 'vscode' +import { Webview, Uri } from 'vscode' import { MessagePublisher } from '../../messages/messagePublisher' import { MessageListener } from '../../messages/messageListener' import { TabType } from '../ui/storages/tabsStorage' @@ -11,35 +11,74 @@ import { getLogger } from '../../../shared/logger' import { amazonqMark } from '../../../shared/performance/marks' import { telemetry } from '../../../shared/telemetry' import { AmazonQChatMessageDuration } from '../../messages/chatMessageDuration' +import { globals, openUrl } from '../../../shared' +import { isClickTelemetry, isOpenAgentTelemetry } from '../ui/telemetry/actions' export function dispatchWebViewMessagesToApps( webview: Webview, webViewToAppsMessagePublishers: Map> ) { webview.onDidReceiveMessage((msg) => { - if (msg.command === 'ui-is-ready') { - /** - * ui-is-ready isn't associated to any tab so just record the telemetry event and continue. - * This would be equivalent of the duration between "user clicked open q" and "ui has become available" - * NOTE: Amazon Q UI is only loaded ONCE. The state is saved between each hide/show of the webview. - */ + switch (msg.command) { + case 'ui-is-ready': { + /** + * ui-is-ready isn't associated to any tab so just record the telemetry event and continue. + * This would be equivalent of the duration between "user clicked open q" and "ui has become available" + * NOTE: Amazon Q UI is only loaded ONCE. The state is saved between each hide/show of the webview. + */ - telemetry.webview_load.emit({ - webviewName: 'amazonq', - duration: performance.measure(amazonqMark.uiReady, amazonqMark.open).duration, - result: 'Succeeded', - }) - performance.clearMarks(amazonqMark.uiReady) - performance.clearMarks(amazonqMark.open) - return - } - - if (msg.type === 'startChatMessageTelemetry') { - AmazonQChatMessageDuration.startChatMessageTelemetry(msg) - return - } else if (msg.type === 'stopChatMessageTelemetry') { - AmazonQChatMessageDuration.stopChatMessageTelemetry(msg) - return + telemetry.webview_load.emit({ + webviewName: 'amazonq', + duration: performance.measure(amazonqMark.uiReady, amazonqMark.open).duration, + result: 'Succeeded', + }) + performance.clearMarks(amazonqMark.uiReady) + performance.clearMarks(amazonqMark.open) + return + } + case 'start-chat-message-telemetry': { + AmazonQChatMessageDuration.startChatMessageTelemetry(msg) + return + } + case 'update-chat-message-telemetry': { + AmazonQChatMessageDuration.updateChatMessageTelemetry(msg) + return + } + case 'stop-chat-message-telemetry': { + AmazonQChatMessageDuration.stopChatMessageTelemetry(msg) + return + } + case 'open-user-guide': { + const { userGuideLink } = msg + void openUrl(Uri.parse(userGuideLink)) + return + } + case 'send-telemetry': { + if (isOpenAgentTelemetry(msg)) { + telemetry.toolkit_openModule.emit({ + module: msg.module, + source: msg.trigger, + result: 'Succeeded', + }) + return + } else if (isClickTelemetry(msg)) { + telemetry.ui_click.emit({ + elementId: msg.source, + result: 'Succeeded', + }) + return + } + return + } + case 'disclaimer-acknowledged': { + globals.globalState.tryUpdate('aws.amazonq.disclaimerAcknowledged', true) + return + } + case 'update-welcome-count': { + const currentLoadCount = globals.globalState.tryGet('aws.amazonq.welcomeChatShowCount', Number, 0) + void globals.globalState.tryUpdate('aws.amazonq.welcomeChatShowCount', currentLoadCount + 1) + return + } } if (msg.type === 'error') { diff --git a/packages/core/src/amazonq/webview/ui/apps/amazonqCommonsConnector.ts b/packages/core/src/amazonq/webview/ui/apps/amazonqCommonsConnector.ts index c5a87857771..c0f031509d3 100644 --- a/packages/core/src/amazonq/webview/ui/apps/amazonqCommonsConnector.ts +++ b/packages/core/src/amazonq/webview/ui/apps/amazonqCommonsConnector.ts @@ -3,15 +3,28 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ChatItemAction } from '@aws/mynah-ui' +import { ChatItem, ChatItemAction, ChatItemType, ChatPrompt } from '@aws/mynah-ui' import { ExtensionMessage } from '../commands' import { AuthFollowUpType } from '../followUps/generator' +import { getTabCommandFromTabType, isTabType, TabType } from '../storages/tabsStorage' +import { + docUserGuide, + userGuideURL as featureDevUserGuide, + helpMessage, + reviewGuideUrl, + testGuideUrl, +} from '../texts/constants' +import { linkToDocsHome } from '../../../../codewhisperer/models/constants' +import { createClickTelemetry, createOpenAgentTelemetry } from '../telemetry/actions' export type WelcomeFollowupType = 'continue-to-chat' export interface ConnectorProps { sendMessageToExtension: (message: ExtensionMessage) => void onWelcomeFollowUpClicked: (tabID: string, welcomeFollowUpType: WelcomeFollowupType) => void + onNewTab: (tabType: TabType) => void + handleCommand: (chatPrompt: ChatPrompt, tabId: string) => void + sendStaticMessages: (tabID: string, messages: ChatItem[]) => void } export interface CodeReference { licenseName?: string @@ -26,10 +39,16 @@ export interface CodeReference { export class Connector { private readonly sendMessageToExtension private readonly onWelcomeFollowUpClicked + private readonly onNewTab + private readonly handleCommand + private readonly sendStaticMessage constructor(props: ConnectorProps) { this.sendMessageToExtension = props.sendMessageToExtension this.onWelcomeFollowUpClicked = props.onWelcomeFollowUpClicked + this.onNewTab = props.onNewTab + this.handleCommand = props.handleCommand + this.sendStaticMessage = props.sendStaticMessages } followUpClicked = (tabID: string, followUp: ChatItemAction): void => { @@ -46,4 +65,90 @@ export class Connector { tabType, }) } + + handleMessageReceive = async (messageData: any): Promise => { + if (messageData.command === 'showExploreAgentsView') { + this.onNewTab('agentWalkthrough') + return + } else if (messageData.command === 'review') { + this.onNewTab('review') + return + } + } + + onCustomFormAction( + tabId: string, + action: { + id: string + text?: string | undefined + formItemValues?: Record | undefined + } + ) { + const tabType = action.id.split('-')[2] + if (!isTabType(tabType)) { + return + } + + if (action.id.startsWith('user-guide-')) { + this.processUserGuideLink(tabType, action.id) + return + } + + if (action.id.startsWith('quick-start-')) { + this.handleCommand( + { + command: getTabCommandFromTabType(tabType), + }, + tabId + ) + + this.sendMessageToExtension(createOpenAgentTelemetry(tabType, 'quick-start')) + } + } + + private processUserGuideLink(tabType: TabType, actionId: string) { + let userGuideLink = '' + switch (tabType) { + case 'featuredev': + userGuideLink = featureDevUserGuide + break + case 'testgen': + userGuideLink = testGuideUrl + break + case 'review': + userGuideLink = reviewGuideUrl + break + case 'doc': + userGuideLink = docUserGuide + break + case 'gumby': + userGuideLink = linkToDocsHome + break + } + + // e.g. amazonq-explore-user-guide-featuredev + this.sendMessageToExtension(createClickTelemetry(`amazonq-explore-${actionId}`)) + + this.sendMessageToExtension({ + command: 'open-user-guide', + userGuideLink, + }) + } + + sendMessage(tabID: string, message: 'help') { + switch (message) { + case 'help': + this.sendStaticMessage(tabID, [ + { + type: ChatItemType.PROMPT, + body: 'How can Amazon Q help me?', + }, + { + type: ChatItemType.ANSWER, + body: helpMessage, + }, + ]) + break + } + } } diff --git a/packages/core/src/amazonq/webview/ui/apps/baseConnector.ts b/packages/core/src/amazonq/webview/ui/apps/baseConnector.ts new file mode 100644 index 00000000000..e28c599d910 --- /dev/null +++ b/packages/core/src/amazonq/webview/ui/apps/baseConnector.ts @@ -0,0 +1,298 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItem, ChatItemAction, ChatItemType, FeedbackPayload } from '@aws/mynah-ui' +import { ExtensionMessage } from '../commands' +import { CodeReference } from './amazonqCommonsConnector' +import { TabOpenType, TabsStorage, TabType } from '../storages/tabsStorage' +import { FollowUpGenerator } from '../followUps/generator' +import { CWCChatItem } from '../connector' + +interface ChatPayload { + chatMessage: string + chatCommand?: string +} + +export interface BaseConnectorProps { + sendMessageToExtension: (message: ExtensionMessage) => void + onMessageReceived?: (tabID: string, messageData: any, needToShowAPIDocsTab: boolean) => void + onChatAnswerReceived?: (tabID: string, message: CWCChatItem | ChatItem, messageData: any) => void + onError: (tabID: string, message: string, title: string) => void + onWarning: (tabID: string, message: string, title: string) => void + onOpenSettingsMessage: (tabID: string) => void + tabsStorage: TabsStorage +} + +export abstract class BaseConnector { + protected readonly sendMessageToExtension + protected readonly onError + protected readonly onWarning + protected readonly onChatAnswerReceived + protected readonly onOpenSettingsMessage + protected readonly followUpGenerator: FollowUpGenerator + protected readonly tabsStorage + + abstract getTabType(): TabType + + constructor(props: BaseConnectorProps) { + this.sendMessageToExtension = props.sendMessageToExtension + this.onChatAnswerReceived = props.onChatAnswerReceived + this.onWarning = props.onWarning + this.onError = props.onError + this.onOpenSettingsMessage = props.onOpenSettingsMessage + this.tabsStorage = props.tabsStorage + this.followUpGenerator = new FollowUpGenerator() + } + + onResponseBodyLinkClick = (tabID: string, messageId: string, link: string): void => { + this.sendMessageToExtension({ + command: 'response-body-link-click', + tabID, + messageId, + link, + tabType: this.getTabType(), + }) + } + onInfoLinkClick = (tabID: string, link: string): void => { + this.sendMessageToExtension({ + command: 'footer-info-link-click', + tabID, + link, + tabType: this.getTabType(), + }) + } + + followUpClicked = (tabID: string, messageId: string, followUp: ChatItemAction): void => { + /** + * We've pressed on a followup button and should start watching that round trip telemetry + */ + this.sendMessageToExtension({ + command: 'start-chat-message-telemetry', + trigger: 'followUpClicked', + tabID, + traceId: messageId, + tabType: this.getTabType(), + startTime: Date.now(), + }) + this.sendMessageToExtension({ + command: 'follow-up-was-clicked', + followUp, + tabID, + messageId, + tabType: this.getTabType(), + }) + } + + onTabAdd = (tabID: string, tabOpenInteractionType?: TabOpenType): void => { + this.sendMessageToExtension({ + tabID: tabID, + command: 'new-tab-was-created', + tabType: this.getTabType(), + tabOpenInteractionType, + }) + } + + onCodeInsertToCursorPosition = ( + tabID: string, + messageId: string, + code?: string, + type?: 'selection' | 'block', + codeReference?: CodeReference[], + eventId?: string, + codeBlockIndex?: number, + totalCodeBlocks?: number, + userIntent?: string, + codeBlockLanguage?: string + ): void => { + this.sendMessageToExtension({ + tabID: tabID, + messageId, + code, + command: 'insert_code_at_cursor_position', + tabType: this.getTabType(), + insertionTargetType: type, + codeReference, + eventId, + codeBlockIndex, + totalCodeBlocks, + userIntent, + codeBlockLanguage, + }) + } + + onCopyCodeToClipboard = ( + tabID: string, + messageId: string, + code?: string, + type?: 'selection' | 'block', + codeReference?: CodeReference[], + eventId?: string, + codeBlockIndex?: number, + totalCodeBlocks?: number, + userIntent?: string, + codeBlockLanguage?: string + ): void => { + this.sendMessageToExtension({ + tabID: tabID, + messageId, + code, + command: 'code_was_copied_to_clipboard', + tabType: this.getTabType(), + insertionTargetType: type, + codeReference, + eventId, + codeBlockIndex, + totalCodeBlocks, + userIntent, + codeBlockLanguage, + }) + } + + onTabRemove = (tabID: string): void => { + this.sendMessageToExtension({ + tabID: tabID, + command: 'tab-was-removed', + tabType: this.getTabType(), + }) + } + + onTabChange = (tabID: string, prevTabID?: string) => { + this.sendMessageToExtension({ + tabID: tabID, + command: 'tab-was-changed', + tabType: this.getTabType(), + prevTabID, + }) + } + + onStopChatResponse = (tabID: string): void => { + this.sendMessageToExtension({ + tabID: tabID, + command: 'stop-response', + tabType: this.getTabType(), + }) + } + + onChatItemVoted = (tabID: string, messageId: string, vote: 'upvote' | 'downvote'): void => { + this.sendMessageToExtension({ + tabID: tabID, + command: 'chat-item-voted', + messageId, + vote, + tabType: this.getTabType(), + }) + } + onSendFeedback = (tabID: string, feedbackPayload: FeedbackPayload): void | undefined => { + this.sendMessageToExtension({ + command: 'chat-item-feedback', + ...feedbackPayload, + tabType: this.getTabType(), + tabID: tabID, + }) + } + + requestGenerativeAIAnswer = (tabID: string, messageId: string, payload: ChatPayload): Promise => { + /** + * When a user presses "enter" send an event that indicates + * we should start tracking the round trip time for this message + **/ + this.sendMessageToExtension({ + command: 'start-chat-message-telemetry', + trigger: 'onChatPrompt', + tabID, + traceId: messageId, + tabType: this.getTabType(), + startTime: Date.now(), + }) + return new Promise((resolve, reject) => { + this.sendMessageToExtension({ + tabID: tabID, + command: 'chat-prompt', + chatMessage: payload.chatMessage, + chatCommand: payload.chatCommand, + tabType: this.getTabType(), + }) + }) + } + + clearChat = (tabID: string): void => { + this.sendMessageToExtension({ + tabID: tabID, + command: 'clear', + chatMessage: '', + tabType: this.getTabType(), + }) + } + + help = (tabID: string): void => { + this.sendMessageToExtension({ + tabID: tabID, + command: 'help', + chatMessage: '', + tabType: this.getTabType(), + }) + } + + onTabOpen = (tabID: string): void => { + this.sendMessageToExtension({ + tabID, + command: 'new-tab-was-created', + tabType: this.getTabType(), + }) + } + + protected sendTriggerMessageProcessed = async (requestID: any): Promise => { + this.sendMessageToExtension({ + command: 'trigger-message-processed', + requestID: requestID, + tabType: this.getTabType(), + }) + } + + protected processAuthNeededException = async (messageData: any): Promise => { + if (this.onChatAnswerReceived === undefined) { + return + } + + this.onChatAnswerReceived( + messageData.tabID, + { + type: ChatItemType.ANSWER, + messageId: messageData.triggerID, + body: messageData.message, + followUp: this.followUpGenerator.generateAuthFollowUps(this.getTabType(), messageData.authType), + canBeVoted: false, + }, + messageData + ) + + return + } + + protected processOpenSettingsMessage = async (messageData: any): Promise => { + this.onOpenSettingsMessage(messageData.tabID) + } + + protected baseHandleMessageReceive = async (messageData: any): Promise => { + if (messageData.type === 'errorMessage') { + this.onError(messageData.tabID, messageData.message, messageData.title) + return + } + if (messageData.type === 'showInvalidTokenNotification') { + this.onWarning(messageData.tabID, messageData.message, messageData.title) + return + } + + if (messageData.type === 'authNeededException') { + await this.processAuthNeededException(messageData) + return + } + + if (messageData.type === 'openSettingsMessage') { + await this.processOpenSettingsMessage(messageData) + return + } + } +} diff --git a/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts b/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts index ff4f489dc75..58f525857a8 100644 --- a/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts +++ b/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts @@ -3,47 +3,25 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ChatItem, ChatItemAction, ChatItemType, FeedbackPayload } from '@aws/mynah-ui' -import { ExtensionMessage } from '../commands' -import { CodeReference } from './amazonqCommonsConnector' -import { TabOpenType, TabsStorage } from '../storages/tabsStorage' -import { FollowUpGenerator } from '../followUps/generator' -import { TracedChatItem } from '../connector' +import { ChatItemType } from '@aws/mynah-ui' +import { TabType } from '../storages/tabsStorage' +import { CWCChatItem } from '../connector' +import { BaseConnector, BaseConnectorProps } from './baseConnector' -interface ChatPayload { - chatMessage: string - traceId?: string - chatCommand?: string +export interface ConnectorProps extends BaseConnectorProps { + onCWCContextCommandMessage: (message: CWCChatItem, command?: string) => string | undefined } -export interface ConnectorProps { - sendMessageToExtension: (message: ExtensionMessage) => void - onMessageReceived?: (tabID: string, messageData: any, needToShowAPIDocsTab: boolean) => void - onChatAnswerReceived?: (tabID: string, message: ChatItem) => void - onCWCContextCommandMessage: (message: ChatItem, command?: string) => string | undefined - onError: (tabID: string, message: string, title: string) => void - onWarning: (tabID: string, message: string, title: string) => void - onOpenSettingsMessage: (tabID: string) => void - tabsStorage: TabsStorage -} - -export class Connector { - private readonly sendMessageToExtension - private readonly onError - private readonly onWarning - private readonly onChatAnswerReceived +export class Connector extends BaseConnector { private readonly onCWCContextCommandMessage - private readonly onOpenSettingsMessage - private readonly followUpGenerator: FollowUpGenerator + + override getTabType(): TabType { + return 'cwc' + } constructor(props: ConnectorProps) { - this.sendMessageToExtension = props.sendMessageToExtension - this.onChatAnswerReceived = props.onChatAnswerReceived - this.onWarning = props.onWarning - this.onError = props.onError + super(props) this.onCWCContextCommandMessage = props.onCWCContextCommandMessage - this.onOpenSettingsMessage = props.onOpenSettingsMessage - this.followUpGenerator = new FollowUpGenerator() } onSourceLinkClick = (tabID: string, messageId: string, link: string): void => { @@ -52,172 +30,7 @@ export class Connector { tabID, messageId, link, - tabType: 'cwc', - }) - } - onResponseBodyLinkClick = (tabID: string, messageId: string, link: string): void => { - this.sendMessageToExtension({ - command: 'response-body-link-click', - tabID, - messageId, - link, - tabType: 'cwc', - }) - } - onInfoLinkClick = (tabID: string, link: string): void => { - this.sendMessageToExtension({ - command: 'footer-info-link-click', - tabID, - link, - tabType: 'cwc', - }) - } - - followUpClicked = (tabID: string, messageId: string, followUp: ChatItemAction): void => { - this.sendMessageToExtension({ - command: 'follow-up-was-clicked', - followUp, - tabID, - messageId, - tabType: 'cwc', - }) - } - - onTabAdd = (tabID: string, tabOpenInteractionType?: TabOpenType): void => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'new-tab-was-created', - tabType: 'cwc', - tabOpenInteractionType, - }) - } - - onCodeInsertToCursorPosition = ( - tabID: string, - messageId: string, - code?: string, - type?: 'selection' | 'block', - codeReference?: CodeReference[], - eventId?: string, - codeBlockIndex?: number, - totalCodeBlocks?: number - ): void => { - this.sendMessageToExtension({ - tabID: tabID, - messageId, - code, - command: 'insert_code_at_cursor_position', - tabType: 'cwc', - insertionTargetType: type, - codeReference, - eventId, - codeBlockIndex, - totalCodeBlocks, - }) - } - - onCopyCodeToClipboard = ( - tabID: string, - messageId: string, - code?: string, - type?: 'selection' | 'block', - codeReference?: CodeReference[], - eventId?: string, - codeBlockIndex?: number, - totalCodeBlocks?: number - ): void => { - this.sendMessageToExtension({ - tabID: tabID, - messageId, - code, - command: 'code_was_copied_to_clipboard', - tabType: 'cwc', - insertionTargetType: type, - codeReference, - eventId, - codeBlockIndex, - totalCodeBlocks, - }) - } - - onTabRemove = (tabID: string): void => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'tab-was-removed', - tabType: 'cwc', - }) - } - - onTabChange = (tabID: string, prevTabID?: string) => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'tab-was-changed', - tabType: 'cwc', - prevTabID, - }) - } - - onStopChatResponse = (tabID: string): void => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'stop-response', - tabType: 'cwc', - }) - } - - onChatItemVoted = (tabID: string, messageId: string, vote: 'upvote' | 'downvote'): void => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'chat-item-voted', - messageId, - vote, - tabType: 'cwc', - }) - } - onSendFeedback = (tabID: string, feedbackPayload: FeedbackPayload): void | undefined => { - this.sendMessageToExtension({ - command: 'chat-item-feedback', - ...feedbackPayload, - tabType: 'cwc', - tabID: tabID, - }) - } - - requestGenerativeAIAnswer = (tabID: string, payload: ChatPayload): Promise => - new Promise((resolve, reject) => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'chat-prompt', - chatMessage: payload.chatMessage, - chatCommand: payload.chatCommand, - traceId: payload.traceId, - tabType: 'cwc', - }) - }) - - clearChat = (tabID: string): void => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'clear', - chatMessage: '', - tabType: 'cwc', - }) - } - - help = (tabID: string): void => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'help', - chatMessage: '', - tabType: 'cwc', - }) - } - - private sendTriggerMessageProcessed = async (requestID: any): Promise => { - this.sendMessageToExtension({ - command: 'trigger-message-processed', - requestID: requestID, - tabType: 'cwc', + tabType: this.getTabType(), }) } @@ -240,7 +53,7 @@ export class Connector { command: 'trigger-tabID-received', triggerID, tabID, - tabType: 'cwc', + tabType: this.getTabType(), }) } @@ -261,14 +74,15 @@ export class Connector { } : undefined - const answer: TracedChatItem = { + const answer: CWCChatItem = { type: messageData.messageType, messageId: messageData.messageID ?? messageData.triggerID, body: messageData.message, followUp: followUps, canBeVoted: true, codeReference: messageData.codeReference, - traceId: messageData.traceId, + userIntent: messageData.userIntent, + codeBlockLanguage: messageData.codeBlockLanguage, } // If it is not there we will not set it @@ -282,7 +96,7 @@ export class Connector { content: messageData.relatedSuggestions, } } - this.onChatAnswerReceived(messageData.tabID, answer) + this.onChatAnswerReceived(messageData.tabID, answer, messageData) // Exit the function if we received an answer from AI if ( @@ -295,12 +109,14 @@ export class Connector { return } if (messageData.messageType === ChatItemType.ANSWER) { - const answer: TracedChatItem = { + const answer: CWCChatItem = { type: messageData.messageType, body: undefined, relatedContent: undefined, messageId: messageData.messageID, codeReference: messageData.codeReference, + userIntent: messageData.userIntent, + codeBlockLanguage: messageData.codeBlockLanguage, followUp: messageData.followUps !== undefined && messageData.followUps.length > 0 ? { @@ -308,44 +124,14 @@ export class Connector { options: messageData.followUps, } : undefined, - traceId: messageData.traceId, } - this.onChatAnswerReceived(messageData.tabID, answer) + this.onChatAnswerReceived(messageData.tabID, answer, messageData) return } } - private processAuthNeededException = async (messageData: any): Promise => { - if (this.onChatAnswerReceived === undefined) { - return - } - - this.onChatAnswerReceived(messageData.tabID, { - type: ChatItemType.ANSWER, - messageId: messageData.triggerID, - body: messageData.message, - followUp: this.followUpGenerator.generateAuthFollowUps('cwc', messageData.authType), - canBeVoted: false, - }) - - return - } - - private processOpenSettingsMessage = async (messageData: any): Promise => { - this.onOpenSettingsMessage(messageData.tabID) - } - handleMessageReceive = async (messageData: any): Promise => { - if (messageData.type === 'errorMessage') { - this.onError(messageData.tabID, messageData.message, messageData.title) - return - } - if (messageData.type === 'showInvalidTokenNotification') { - this.onWarning(messageData.tabID, messageData.message, messageData.title) - return - } - if (messageData.type === 'chatMessage') { await this.processChatMessage(messageData) return @@ -356,14 +142,7 @@ export class Connector { return } - if (messageData.type === 'authNeededException') { - await this.processAuthNeededException(messageData) - return - } - - if (messageData.type === 'openSettingsMessage') { - await this.processOpenSettingsMessage(messageData) - return - } + // For other message types, call the base class handleMessageReceive + await this.baseHandleMessageReceive(messageData) } } diff --git a/packages/core/src/amazonq/webview/ui/apps/docChatConnector.ts b/packages/core/src/amazonq/webview/ui/apps/docChatConnector.ts new file mode 100644 index 00000000000..efabe2be4f5 --- /dev/null +++ b/packages/core/src/amazonq/webview/ui/apps/docChatConnector.ts @@ -0,0 +1,229 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItem, ChatItemType, FeedbackPayload, MynahIcons, ProgressField } from '@aws/mynah-ui' +import { TabType } from '../storages/tabsStorage' +import { DiffTreeFileInfo } from '../diffTree/types' +import { BaseConnectorProps, BaseConnector } from './baseConnector' + +export interface ConnectorProps extends BaseConnectorProps { + onAsyncEventProgress: (tabID: string, inProgress: boolean, message: string) => void + sendFeedback?: (tabId: string, feedbackPayload: FeedbackPayload) => void | undefined + onFileComponentUpdate: ( + tabID: string, + filePaths: DiffTreeFileInfo[], + deletedFiles: DiffTreeFileInfo[], + messageId: string, + disableFileActions: boolean + ) => void + onFileActionClick: (tabID: string, messageId: string, filePath: string, actionName: string) => void + onUpdatePlaceholder: (tabID: string, newPlaceholder: string) => void + onUpdatePromptProgress: (tabID: string, progressField: ProgressField) => void + onChatInputEnabled: (tabID: string, enabled: boolean) => void + onUpdateAuthentication: (featureDevEnabled: boolean, authenticatingTabIDs: string[]) => void + onNewTab: (tabType: TabType) => void +} + +export class Connector extends BaseConnector { + private readonly onFileComponentUpdate + private readonly onAsyncEventProgress + private readonly updatePlaceholder + private readonly chatInputEnabled + private readonly onUpdateAuthentication + private readonly onNewTab + private readonly updatePromptProgress + + override getTabType(): TabType { + return 'doc' + } + + constructor(props: ConnectorProps) { + super(props) + this.onFileComponentUpdate = props.onFileComponentUpdate + this.onAsyncEventProgress = props.onAsyncEventProgress + this.updatePlaceholder = props.onUpdatePlaceholder + this.chatInputEnabled = props.onChatInputEnabled + this.onUpdateAuthentication = props.onUpdateAuthentication + this.onNewTab = props.onNewTab + this.updatePromptProgress = props.onUpdatePromptProgress + } + + onOpenDiff = (tabID: string, filePath: string, deleted: boolean): void => { + this.sendMessageToExtension({ + command: 'open-diff', + tabID, + filePath, + deleted, + tabType: this.getTabType(), + }) + } + onFileActionClick = (tabID: string, messageId: string, filePath: string, actionName: string): void => { + this.sendMessageToExtension({ + command: 'file-click', + tabID, + messageId, + filePath, + actionName, + tabType: this.getTabType(), + }) + } + + private processFolderConfirmationMessage = async (messageData: any, folderPath: string): Promise => { + if (this.onChatAnswerReceived !== undefined) { + const answer: ChatItem = { + type: ChatItemType.ANSWER, + body: messageData.message ?? undefined, + messageId: messageData.messageID ?? messageData.triggerID ?? '', + fileList: { + rootFolderTitle: undefined, + fileTreeTitle: '', + filePaths: [folderPath], + details: { + [folderPath]: { + icon: MynahIcons.FOLDER, + clickable: false, + }, + }, + }, + followUp: { + text: '', + options: messageData.followUps, + }, + } + this.onChatAnswerReceived(messageData.tabID, answer, messageData) + } + } + + private processChatMessage = async (messageData: any): Promise => { + if (this.onChatAnswerReceived !== undefined) { + const answer: ChatItem = { + type: messageData.messageType, + body: messageData.message ?? undefined, + messageId: messageData.messageID ?? messageData.triggerID ?? '', + relatedContent: undefined, + canBeVoted: messageData.canBeVoted, + snapToTop: messageData.snapToTop, + followUp: + messageData.followUps !== undefined && messageData.followUps.length > 0 + ? { + text: + messageData.messageType === ChatItemType.SYSTEM_PROMPT + ? '' + : 'Select one of the following...', + options: messageData.followUps, + } + : undefined, + } + this.onChatAnswerReceived(messageData.tabID, answer, messageData) + } + } + + private processCodeResultMessage = async (messageData: any): Promise => { + if (this.onChatAnswerReceived !== undefined) { + const answer: ChatItem = { + type: ChatItemType.ANSWER, + relatedContent: undefined, + followUp: undefined, + canBeVoted: false, + codeReference: messageData.references, + // TODO get the backend to store a message id in addition to conversationID + messageId: + messageData.codeGenerationId ?? + messageData.messageID ?? + messageData.triggerID ?? + messageData.conversationID, + fileList: { + rootFolderTitle: 'Documentation', + fileTreeTitle: 'Documents ready', + filePaths: messageData.filePaths.map((f: DiffTreeFileInfo) => f.zipFilePath), + deletedFiles: messageData.deletedFiles.map((f: DiffTreeFileInfo) => f.zipFilePath), + }, + body: '', + } + this.onChatAnswerReceived(messageData.tabID, answer, messageData) + } + } + + handleMessageReceive = async (messageData: any): Promise => { + if (messageData.type === 'updateFileComponent') { + this.onFileComponentUpdate( + messageData.tabID, + messageData.filePaths, + messageData.deletedFiles, + messageData.messageId, + messageData.disableFileActions + ) + return + } + + if (messageData.type === 'chatMessage') { + await this.processChatMessage(messageData) + return + } + + if (messageData.type === 'folderConfirmationMessage') { + await this.processFolderConfirmationMessage(messageData, messageData.folderPath) + return + } + + if (messageData.type === 'codeResultMessage') { + await this.processCodeResultMessage(messageData) + return + } + + if (messageData.type === 'asyncEventProgressMessage') { + this.onAsyncEventProgress(messageData.tabID, messageData.inProgress, messageData.message ?? undefined) + return + } + + if (messageData.type === 'updatePlaceholderMessage') { + this.updatePlaceholder(messageData.tabID, messageData.newPlaceholder) + return + } + + if (messageData.type === 'chatInputEnabledMessage') { + this.chatInputEnabled(messageData.tabID, messageData.enabled) + return + } + + if (messageData.type === 'authenticationUpdateMessage') { + this.onUpdateAuthentication(messageData.featureEnabled, messageData.authenticatingTabIDs) + return + } + + if (messageData.type === 'openNewTabMessage') { + this.onNewTab(this.getTabType()) + return + } + + if (messageData.type === 'updatePromptProgress') { + this.updatePromptProgress(messageData.tabID, messageData.progressField) + return + } + + // For other message types, call the base class handleMessageReceive + await this.baseHandleMessageReceive(messageData) + } + + onCustomFormAction( + tabId: string, + action: { + id: string + text?: string | undefined + formItemValues?: Record | undefined + } + ) { + if (action === undefined) { + return + } + this.sendMessageToExtension({ + command: 'form-action-click', + action: action.id, + formSelectedValues: action.formItemValues, + tabType: 'doc', + tabID: tabId, + }) + } +} diff --git a/packages/core/src/amazonq/webview/ui/apps/featureDevChatConnector.ts b/packages/core/src/amazonq/webview/ui/apps/featureDevChatConnector.ts index 37657c803cf..69eb9f1c716 100644 --- a/packages/core/src/amazonq/webview/ui/apps/featureDevChatConnector.ts +++ b/packages/core/src/amazonq/webview/ui/apps/featureDevChatConnector.ts @@ -3,104 +3,68 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ChatItem, ChatItemAction, ChatItemType, FeedbackPayload } from '@aws/mynah-ui' -import { ExtensionMessage } from '../commands' -import { TabType, TabsStorage } from '../storages/tabsStorage' -import { CodeReference } from './amazonqCommonsConnector' -import { FollowUpGenerator } from '../followUps/generator' +import { ChatItem, ChatItemType, FeedbackPayload } from '@aws/mynah-ui' +import { TabType } from '../storages/tabsStorage' import { getActions } from '../diffTree/actions' import { DiffTreeFileInfo } from '../diffTree/types' +import { BaseConnector, BaseConnectorProps } from './baseConnector' -interface ChatPayload { - chatMessage: string -} - -export interface ConnectorProps { - sendMessageToExtension: (message: ExtensionMessage) => void - onMessageReceived?: (tabID: string, messageData: any, needToShowAPIDocsTab: boolean) => void - onAsyncEventProgress: (tabID: string, inProgress: boolean, message: string) => void - onChatAnswerReceived?: (tabID: string, message: ChatItem) => void +export interface ConnectorProps extends BaseConnectorProps { + onAsyncEventProgress: ( + tabID: string, + inProgress: boolean, + message: string, + messageId: string | undefined, + enableStopAction: boolean + ) => void + onChatAnswerUpdated?: (tabID: string, message: ChatItem) => void sendFeedback?: (tabId: string, feedbackPayload: FeedbackPayload) => void | undefined - onError: (tabID: string, message: string, title: string) => void - onWarning: (tabID: string, message: string, title: string) => void onFileComponentUpdate: ( tabID: string, filePaths: DiffTreeFileInfo[], deletedFiles: DiffTreeFileInfo[], - messageId: string + messageId: string, + disableFileActions: boolean ) => void onFileActionClick: (tabID: string, messageId: string, filePath: string, actionName: string) => void onUpdatePlaceholder: (tabID: string, newPlaceholder: string) => void onChatInputEnabled: (tabID: string, enabled: boolean) => void onUpdateAuthentication: (featureDevEnabled: boolean, authenticatingTabIDs: string[]) => void onNewTab: (tabType: TabType) => void - tabsStorage: TabsStorage } -export class Connector { - private readonly sendMessageToExtension - private readonly onError - private readonly onWarning +export class Connector extends BaseConnector { private readonly onFileComponentUpdate - private readonly onChatAnswerReceived + private readonly onChatAnswerUpdated private readonly onAsyncEventProgress private readonly updatePlaceholder private readonly chatInputEnabled private readonly onUpdateAuthentication - private readonly followUpGenerator: FollowUpGenerator private readonly onNewTab + override getTabType(): TabType { + return 'featuredev' + } + constructor(props: ConnectorProps) { - this.sendMessageToExtension = props.sendMessageToExtension - this.onChatAnswerReceived = props.onChatAnswerReceived - this.onWarning = props.onWarning + super(props) this.onFileComponentUpdate = props.onFileComponentUpdate - this.onError = props.onError this.onAsyncEventProgress = props.onAsyncEventProgress this.updatePlaceholder = props.onUpdatePlaceholder this.chatInputEnabled = props.onChatInputEnabled this.onUpdateAuthentication = props.onUpdateAuthentication - this.followUpGenerator = new FollowUpGenerator() this.onNewTab = props.onNewTab + this.onChatAnswerUpdated = props.onChatAnswerUpdated } - onCodeInsertToCursorPosition = ( - tabID: string, - code?: string, - type?: 'selection' | 'block', - codeReference?: CodeReference[] - ): void => { - this.sendMessageToExtension({ - tabID: tabID, - code, - command: 'insert_code_at_cursor_position', - codeReference, - tabType: 'featuredev', - }) - } - - onCopyCodeToClipboard = ( - tabID: string, - code?: string, - type?: 'selection' | 'block', - codeReference?: CodeReference[] - ): void => { - this.sendMessageToExtension({ - tabID: tabID, - code, - command: 'code_was_copied_to_clipboard', - codeReference, - tabType: 'featuredev', - }) - } - - onOpenDiff = (tabID: string, filePath: string, deleted: boolean): void => { + onOpenDiff = (tabID: string, filePath: string, deleted: boolean, messageId?: string): void => { this.sendMessageToExtension({ command: 'open-diff', tabID, filePath, deleted, - tabType: 'featuredev', + messageId, + tabType: this.getTabType(), }) } onFileActionClick = (tabID: string, messageId: string, filePath: string, actionName: string): void => { @@ -110,55 +74,53 @@ export class Connector { messageId, filePath, actionName, - tabType: 'featuredev', + tabType: this.getTabType(), }) } - followUpClicked = (tabID: string, followUp: ChatItemAction): void => { - this.sendMessageToExtension({ - command: 'follow-up-was-clicked', - followUp, - tabID, - tabType: 'featuredev', - }) + private createAnswer = (messageData: any): ChatItem => { + return { + type: messageData.messageType, + body: messageData.message ?? undefined, + messageId: messageData.messageId ?? messageData.messageID ?? messageData.triggerID ?? '', + relatedContent: undefined, + canBeVoted: messageData.canBeVoted ?? undefined, + snapToTop: messageData.snapToTop ?? undefined, + followUp: + messageData.followUps !== undefined && Array.isArray(messageData.followUps) + ? { + text: + messageData.messageType === ChatItemType.SYSTEM_PROMPT || + messageData.followUps.length === 0 + ? '' + : 'Please follow up with one of these', + options: messageData.followUps, + } + : undefined, + } } - requestGenerativeAIAnswer = (tabID: string, payload: ChatPayload): Promise => - new Promise((resolve, reject) => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'chat-prompt', - chatMessage: payload.chatMessage, - tabType: 'featuredev', - }) - }) - private processChatMessage = async (messageData: any): Promise => { if (this.onChatAnswerReceived !== undefined) { - const answer: ChatItem = { - type: messageData.messageType, - body: messageData.message ?? undefined, - messageId: messageData.messageID ?? messageData.triggerID ?? '', - relatedContent: undefined, - canBeVoted: messageData.canBeVoted, - snapToTop: messageData.snapToTop, - followUp: - messageData.followUps !== undefined && messageData.followUps.length > 0 - ? { - text: - messageData.messageType === ChatItemType.SYSTEM_PROMPT - ? '' - : 'Please follow up with one of these', - options: messageData.followUps, - } - : undefined, - } - this.onChatAnswerReceived(messageData.tabID, answer) + const answer = this.createAnswer(messageData) + this.onChatAnswerReceived(messageData.tabID, answer, messageData) } } private processCodeResultMessage = async (messageData: any): Promise => { if (this.onChatAnswerReceived !== undefined) { + const messageId = + messageData.codeGenerationId ?? + messageData.messageId ?? + messageData.messageID ?? + messageData.triggerID ?? + messageData.conversationID + this.sendMessageToExtension({ + tabID: messageData.tabID, + command: 'store-code-result-message-id', + messageId, + tabType: 'featuredev', + }) const actions = getActions([...messageData.filePaths, ...messageData.deletedFiles]) const answer: ChatItem = { type: ChatItemType.ANSWER, @@ -166,8 +128,7 @@ export class Connector { followUp: undefined, canBeVoted: true, codeReference: messageData.references, - // TODO get the backend to store a message id in addition to conversationID - messageId: messageData.messageID ?? messageData.triggerID ?? messageData.conversationID, + messageId, fileList: { rootFolderTitle: 'Changes', filePaths: messageData.filePaths.map((f: DiffTreeFileInfo) => f.zipFilePath), @@ -176,30 +137,8 @@ export class Connector { }, body: '', } - this.onChatAnswerReceived(messageData.tabID, answer) - } - } - - private processAuthNeededException = async (messageData: any): Promise => { - if (this.onChatAnswerReceived === undefined) { - return + this.onChatAnswerReceived(messageData.tabID, answer, messageData) } - - this.onChatAnswerReceived(messageData.tabID, { - type: ChatItemType.ANSWER, - body: messageData.message, - followUp: undefined, - canBeVoted: false, - }) - - this.onChatAnswerReceived(messageData.tabID, { - type: ChatItemType.SYSTEM_PROMPT, - body: undefined, - followUp: this.followUpGenerator.generateAuthFollowUps('featuredev', messageData.authType), - canBeVoted: false, - }) - - return } handleMessageReceive = async (messageData: any): Promise => { @@ -208,17 +147,14 @@ export class Connector { messageData.tabID, messageData.filePaths, messageData.deletedFiles, - messageData.messageId + messageData.messageId, + messageData.disableFileActions ) return } - if (messageData.type === 'errorMessage') { - this.onError(messageData.tabID, messageData.message, messageData.title) - return - } - - if (messageData.type === 'showInvalidTokenNotification') { - this.onWarning(messageData.tabID, messageData.message, messageData.title) + if (messageData.type === 'updateChatAnswer') { + const answer = this.createAnswer(messageData) + this.onChatAnswerUpdated?.(messageData.tabID, answer) return } @@ -233,7 +169,14 @@ export class Connector { } if (messageData.type === 'asyncEventProgressMessage') { - this.onAsyncEventProgress(messageData.tabID, messageData.inProgress, messageData.message ?? undefined) + const enableStopAction = true + this.onAsyncEventProgress( + messageData.tabID, + messageData.inProgress, + messageData.message ?? undefined, + messageData.messageId ?? undefined, + enableStopAction + ) return } @@ -248,12 +191,7 @@ export class Connector { } if (messageData.type === 'authenticationUpdateMessage') { - this.onUpdateAuthentication(messageData.featureDevEnabled, messageData.authenticatingTabIDs) - return - } - - if (messageData.type === 'authNeededException') { - await this.processAuthNeededException(messageData) + this.onUpdateAuthentication(messageData.featureEnabled, messageData.authenticatingTabIDs) return } @@ -261,57 +199,17 @@ export class Connector { this.onNewTab('featuredev') return } - } - - onStopChatResponse = (tabID: string): void => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'stop-response', - }) - } - onTabOpen = (tabID: string): void => { - this.sendMessageToExtension({ - tabID, - command: 'new-tab-was-created', - tabType: 'featuredev', - }) - } - - onTabRemove = (tabID: string): void => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'tab-was-removed', - tabType: 'featuredev', - }) + // For other message types, call the base class handleMessageReceive + await this.baseHandleMessageReceive(messageData) } sendFeedback = (tabId: string, feedbackPayload: FeedbackPayload): void | undefined => { this.sendMessageToExtension({ command: 'chat-item-feedback', ...feedbackPayload, - tabType: 'featuredev', - tabID: tabId, - }) - } - - onChatItemVoted = (tabId: string, messageId: string, vote: string): void | undefined => { - this.sendMessageToExtension({ + tabType: this.getTabType(), tabID: tabId, - messageId: messageId, - vote: vote, - command: 'chat-item-voted', - tabType: 'featuredev', - }) - } - - onResponseBodyLinkClick = (tabID: string, messageId: string, link: string): void => { - this.sendMessageToExtension({ - command: 'response-body-link-click', - tabID, - messageId, - link, - tabType: 'featuredev', }) } } diff --git a/packages/core/src/amazonq/webview/ui/apps/gumbyChatConnector.ts b/packages/core/src/amazonq/webview/ui/apps/gumbyChatConnector.ts index 533a20da332..9801ff7d27f 100644 --- a/packages/core/src/amazonq/webview/ui/apps/gumbyChatConnector.ts +++ b/packages/core/src/amazonq/webview/ui/apps/gumbyChatConnector.ts @@ -8,24 +8,24 @@ */ import { ChatItem, ChatItemType } from '@aws/mynah-ui' -import { ExtensionMessage } from '../commands' -import { TabOpenType, TabsStorage } from '../storages/tabsStorage' +import { TabType } from '../storages/tabsStorage' import { GumbyMessageType } from '../../../../amazonqGumby/chat/views/connector/connector' import { ChatPayload } from '../connector' - -export interface ConnectorProps { - sendMessageToExtension: (message: ExtensionMessage) => void - onMessageReceived?: (tabID: string, messageData: any, needToShowAPIDocsTab: boolean) => void - onAsyncEventProgress: (tabID: string, inProgress: boolean, message: string, messageId: string) => void - onChatAnswerReceived?: (tabID: string, message: ChatItem) => void +import { BaseConnector, BaseConnectorProps } from './baseConnector' + +export interface ConnectorProps extends BaseConnectorProps { + onAsyncEventProgress: ( + tabID: string, + inProgress: boolean, + message: string, + messageId: string, + enableStopAction: boolean + ) => void onChatAnswerUpdated?: (tabID: string, message: ChatItem) => void onQuickHandlerCommand: (tabID: string, command: string, eventId?: string) => void - onError: (tabID: string, message: string, title: string) => void - onWarning: (tabID: string, message: string, title: string) => void onUpdateAuthentication: (gumbyEnabled: boolean, authenticatingTabIDs: string[]) => void onChatInputEnabled: (tabID: string, enabled: boolean) => void onUpdatePlaceholder: (tabID: string, newPlaceholder: string) => void - tabsStorage: TabsStorage } export interface MessageData { @@ -33,46 +33,26 @@ export interface MessageData { type: GumbyMessageType } -export class Connector { +export class Connector extends BaseConnector { private readonly onAuthenticationUpdate - private readonly sendMessageToExtension - private readonly onError - private readonly onChatAnswerReceived private readonly onChatAnswerUpdated private readonly chatInputEnabled private readonly onAsyncEventProgress private readonly onQuickHandlerCommand private readonly updatePlaceholder - private readonly tabStorage + + override getTabType(): TabType { + return 'gumby' + } constructor(props: ConnectorProps) { - this.sendMessageToExtension = props.sendMessageToExtension - this.onChatAnswerReceived = props.onChatAnswerReceived + super(props) this.onChatAnswerUpdated = props.onChatAnswerUpdated - this.onError = props.onError this.chatInputEnabled = props.onChatInputEnabled this.onAsyncEventProgress = props.onAsyncEventProgress this.updatePlaceholder = props.onUpdatePlaceholder this.onQuickHandlerCommand = props.onQuickHandlerCommand this.onAuthenticationUpdate = props.onUpdateAuthentication - this.tabStorage = props.tabsStorage - } - - onTabAdd = (tabID: string, tabOpenInteractionType?: TabOpenType): void => { - this.sendMessageToExtension({ - tabID: tabID, - command: 'new-tab-was-created', - tabType: 'gumby', - tabOpenInteractionType, - }) - } - - onTabRemove(tabID: string) { - this.sendMessageToExtension({ - tabID: tabID, - command: 'tab-was-removed', - tabType: 'gumby', - }) } private processChatPrompt = async (messageData: any, tabID: string): Promise => { @@ -90,7 +70,7 @@ export class Connector { canBeVoted: false, } - this.onChatAnswerReceived(tabID, answer) + this.onChatAnswerReceived(tabID, answer, messageData) return } @@ -114,7 +94,7 @@ export class Connector { return } - this.onChatAnswerReceived(messageData.tabID, answer) + this.onChatAnswerReceived(messageData.tabID, answer, messageData) } } @@ -123,29 +103,18 @@ export class Connector { tabID: tabID, command: 'transform', chatMessage: 'transform', - tabType: 'gumby', + tabType: this.getTabType(), }) } requestAnswer = (tabID: string, payload: ChatPayload) => { - this.tabStorage.updateTabStatus(tabID, 'busy') + this.tabsStorage.updateTabStatus(tabID, 'busy') this.sendMessageToExtension({ tabID: tabID, command: 'chat-prompt', chatMessage: payload.chatMessage, chatCommand: payload.chatCommand, - tabType: 'gumby', - }) - } - - private processAuthNeededException = async (messageData: any): Promise => { - if (this.onChatAnswerReceived === undefined) { - return - } - - this.onChatAnswerReceived(messageData.tabID, { - type: ChatItemType.SYSTEM_PROMPT, - body: messageData.message, + tabType: this.getTabType(), }) } @@ -165,21 +134,11 @@ export class Connector { command: 'form-action-click', action: action.id, formSelectedValues: action.formItemValues, - tabType: 'gumby', + tabType: this.getTabType(), tabID: tabId, }) } - onResponseBodyLinkClick = (tabID: string, messageId: string, link: string): void => { - this.sendMessageToExtension({ - command: 'response-body-link-click', - tabID, - messageId, - link, - tabType: 'gumby', - }) - } - private processExecuteCommand = async (messageData: any): Promise => { this.onQuickHandlerCommand(messageData.tabID, messageData.command, messageData.eventId) } @@ -192,12 +151,10 @@ export class Connector { messageData.tabID, messageData.inProgress, messageData.message, - messageData.messageId + messageData.messageId, + false ) break - case 'authNeededException': - await this.processAuthNeededException(messageData) - break case 'authenticationUpdateMessage': this.onAuthenticationUpdate(messageData.gumbyEnabled, messageData.authenticatingTabIDs) break @@ -210,15 +167,14 @@ export class Connector { case 'chatPrompt': await this.processChatPrompt(messageData, messageData.tabID) break - case 'errorMessage': - this.onError(messageData.tabID, messageData.message, messageData.title) - break case 'sendCommandMessage': await this.processExecuteCommand(messageData) break case 'updatePlaceholderMessage': this.updatePlaceholder(messageData.tabID, messageData.newPlaceholder) break + default: + await this.baseHandleMessageReceive(messageData) } } } diff --git a/packages/core/src/amazonq/webview/ui/apps/scanChatConnector.ts b/packages/core/src/amazonq/webview/ui/apps/scanChatConnector.ts new file mode 100644 index 00000000000..2087be77234 --- /dev/null +++ b/packages/core/src/amazonq/webview/ui/apps/scanChatConnector.ts @@ -0,0 +1,191 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * This class is responsible for listening to and processing events + * from the webview and translating them into events to be handled by the extension, + * and events from the extension and translating them into events to be handled by the webview. + */ + +import { ChatItem, ChatItemType, ProgressField } from '@aws/mynah-ui' +import { ExtensionMessage } from '../commands' +import { TabsStorage, TabType } from '../storages/tabsStorage' +import { ScanMessageType } from '../../../../amazonqScan/connector' +import { BaseConnector, BaseConnectorProps } from './baseConnector' + +export interface ConnectorProps extends BaseConnectorProps { + sendMessageToExtension: (message: ExtensionMessage) => void + onChatAnswerReceived?: (tabID: string, message: ChatItem, messageData: any) => void + onQuickHandlerCommand: (tabID: string, command: string, eventId?: string) => void + onWarning: (tabID: string, message: string, title: string) => void + onError: (tabID: string, message: string, title: string) => void + onUpdateAuthentication: (scanEnabled: boolean, authenticatingTabIDs: string[]) => void + onChatInputEnabled: (tabID: string, enabled: boolean) => void + onUpdatePlaceholder: (tabID: string, newPlaceholder: string) => void + onUpdatePromptProgress: (tabID: string, progressField: ProgressField) => void + tabsStorage: TabsStorage +} + +export interface MessageData { + tabID: string + type: ScanMessageType +} + +export class Connector extends BaseConnector { + override getTabType(): TabType { + return 'review' + } + readonly onAuthenticationUpdate + override readonly sendMessageToExtension + override readonly onError + override readonly onChatAnswerReceived + private readonly chatInputEnabled + private readonly onQuickHandlerCommand + private readonly updatePlaceholder + private readonly updatePromptProgress + + constructor(props: ConnectorProps) { + super(props) + this.sendMessageToExtension = props.sendMessageToExtension + this.onChatAnswerReceived = props.onChatAnswerReceived + this.onError = props.onError + this.chatInputEnabled = props.onChatInputEnabled + this.updatePlaceholder = props.onUpdatePlaceholder + this.updatePromptProgress = props.onUpdatePromptProgress + this.onQuickHandlerCommand = props.onQuickHandlerCommand + this.onAuthenticationUpdate = props.onUpdateAuthentication + } + + scan = (tabID: string): void => { + this.sendMessageToExtension({ + tabID: tabID, + command: 'review', + chatMessage: '', + tabType: 'review', + }) + } + + onCustomFormAction( + tabId: string, + action: { + id: string + text?: string | undefined + formItemValues?: Record | undefined + } + ) { + if (action === undefined) { + return + } + this.sendMessageToExtension({ + command: 'form-action-click', + action: action.id, + formSelectedValues: action.formItemValues, + tabType: 'review', + tabID: tabId, + }) + } + + private processChatPrompt = async (messageData: any, tabID: string): Promise => { + if (this.onChatAnswerReceived === undefined) { + return + } + const answer: ChatItem = { + type: ChatItemType.PROMPT, + body: messageData.message, + followUp: undefined, + status: 'info', + canBeVoted: false, + } + this.onChatAnswerReceived(tabID, answer, messageData) + return + } + + private processExecuteCommand = async (messageData: any): Promise => { + this.onQuickHandlerCommand(messageData.tabID, messageData.command, messageData.eventId) + } + + private processChatMessage = async (messageData: any): Promise => { + if (this.onChatAnswerReceived === undefined) { + return + } + + if (messageData.message !== undefined) { + const answer: ChatItem = { + type: messageData.messageType, + messageId: messageData.messageId ?? messageData.triggerID, + body: messageData.message, + buttons: messageData.buttons ?? [], + canBeVoted: messageData.canBeVoted, + followUp: + messageData.followUps !== undefined && messageData.followUps.length > 0 + ? { + text: '', + options: messageData.followUps, + } + : undefined, + informationCard: messageData.informationCard, + fileList: messageData.fileList, + } + + this.onChatAnswerReceived(messageData.tabID, answer, messageData) + } + } + + override processAuthNeededException = async (messageData: any): Promise => { + if (this.onChatAnswerReceived === undefined) { + return + } + + this.onChatAnswerReceived( + messageData.tabID, + { + type: ChatItemType.SYSTEM_PROMPT, + body: messageData.message, + }, + messageData + ) + } + + // This handles messages received from the extension, to be forwarded to the webview + handleMessageReceive = async (messageData: { type: ScanMessageType } & Record) => { + switch (messageData.type) { + case 'authNeededException': + await this.processAuthNeededException(messageData) + break + case 'authenticationUpdateMessage': + this.onAuthenticationUpdate(messageData.scanEnabled, messageData.authenticatingTabIDs) + break + case 'chatInputEnabledMessage': + this.chatInputEnabled(messageData.tabID, messageData.enabled) + break + case 'chatMessage': + await this.processChatMessage(messageData) + break + case 'updatePlaceholderMessage': + this.updatePlaceholder(messageData.tabID, messageData.newPlaceholder) + break + case 'updatePromptProgress': + this.updatePromptProgress(messageData.tabID, messageData.progressField) + break + case 'chatPrompt': + await this.processChatPrompt(messageData, messageData.tabID) + break + case 'errorMessage': + this.onError(messageData.tabID, messageData.message, messageData.title) + break + case 'sendCommandMessage': + await this.processExecuteCommand(messageData) + break + } + } + + onFileClick = (tabID: string, filePath: string, messageId?: string) => { + this.sendMessageToExtension({ + command: 'file-click', + tabID, + messageId, + filePath, + tabType: 'review', + }) + } +} diff --git a/packages/core/src/amazonq/webview/ui/apps/testChatConnector.ts b/packages/core/src/amazonq/webview/ui/apps/testChatConnector.ts new file mode 100644 index 00000000000..cefc2b8818f --- /dev/null +++ b/packages/core/src/amazonq/webview/ui/apps/testChatConnector.ts @@ -0,0 +1,235 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * This class is responsible for listening to and processing events + * from the webview and translating them into events to be handled by the extension, + * and events from the extension and translating them into events to be handled by the webview. + */ + +import { ChatItem, ChatItemType, MynahIcons, ProgressField } from '@aws/mynah-ui' +import { ExtensionMessage } from '../commands' +import { TabsStorage, TabType } from '../storages/tabsStorage' +import { TestMessageType } from '../../../../amazonqTest/chat/views/connector/connector' +import { ChatPayload } from '../connector' +import { BaseConnector, BaseConnectorProps } from './baseConnector' + +export interface ConnectorProps extends BaseConnectorProps { + sendMessageToExtension: (message: ExtensionMessage) => void + onChatAnswerReceived?: (tabID: string, message: ChatItem, messageData: any) => void + onRunTestMessageReceived?: (tabID: string, showRunTestMessage: boolean) => void + onChatAnswerUpdated?: (tabID: string, message: ChatItem) => void + onQuickHandlerCommand: (tabID: string, command: string, eventId?: string) => void + onWarning: (tabID: string, message: string, title: string) => void + onError: (tabID: string, message: string, title: string) => void + onUpdateAuthentication: (testEnabled: boolean, authenticatingTabIDs: string[]) => void + onChatInputEnabled: (tabID: string, enabled: boolean) => void + onUpdatePlaceholder: (tabID: string, newPlaceholder: string) => void + onUpdatePromptProgress: (tabID: string, progressField: ProgressField) => void + tabsStorage: TabsStorage +} + +export interface MessageData { + tabID: string + type: TestMessageType +} +// TODO: Refactor testChatConnector, scanChatConnector and other apps connector files post RIV +export class Connector extends BaseConnector { + override getTabType(): TabType { + return 'testgen' + } + readonly onAuthenticationUpdate + override readonly sendMessageToExtension + override readonly onChatAnswerReceived + private readonly onChatAnswerUpdated + private readonly chatInputEnabled + private readonly updatePlaceholder + private readonly updatePromptProgress + override readonly onError + private readonly tabStorage + private readonly runTestMessageReceived + + constructor(props: ConnectorProps) { + super(props) + this.runTestMessageReceived = props.onRunTestMessageReceived + this.sendMessageToExtension = props.sendMessageToExtension + this.onChatAnswerReceived = props.onChatAnswerReceived + this.onChatAnswerUpdated = props.onChatAnswerUpdated + this.chatInputEnabled = props.onChatInputEnabled + this.updatePlaceholder = props.onUpdatePlaceholder + this.updatePromptProgress = props.onUpdatePromptProgress + this.onAuthenticationUpdate = props.onUpdateAuthentication + this.onError = props.onError + this.tabStorage = props.tabsStorage + } + + startTestGen(tabID: string, prompt: string) { + this.sendMessageToExtension({ + tabID: tabID, + command: 'start-test-gen', + tabType: 'testgen', + prompt, + }) + } + + requestAnswer = (tabID: string, payload: ChatPayload) => { + this.tabStorage.updateTabStatus(tabID, 'busy') + this.sendMessageToExtension({ + tabID: tabID, + command: 'chat-prompt', + chatMessage: payload.chatMessage, + chatCommand: payload.chatCommand, + tabType: 'testgen', + }) + } + + onCustomFormAction( + tabId: string, + action: { + id: string + text?: string | undefined + description?: string | undefined + formItemValues?: Record | undefined + } + ) { + if (action === undefined) { + return + } + + this.sendMessageToExtension({ + command: 'form-action-click', + action: action.id, + formSelectedValues: action.formItemValues, + tabType: 'testgen', + tabID: tabId, + description: action.description, + }) + } + + onFileDiff = (tabID: string, filePath: string, deleted: boolean, messageId?: string): void => { + // TODO: add this back once we can advance flow from here + // this.sendMessageToExtension({ + // command: 'open-diff', + // tabID, + // filePath, + // deleted, + // messageId, + // tabType: 'testgen', + // }) + } + + private processChatMessage = async (messageData: any): Promise => { + if (this.onChatAnswerReceived === undefined) { + return + } + if (messageData.command === 'test' && this.runTestMessageReceived) { + this.runTestMessageReceived(messageData.tabID, true) + return + } + if (messageData.message !== undefined) { + const answer: ChatItem = { + type: messageData.messageType, + messageId: messageData.messageId ?? messageData.triggerID, + body: messageData.message, + canBeVoted: false, + informationCard: messageData.informationCard, + } + this.onChatAnswerReceived(messageData.tabID, answer, messageData) + } + } + // Displays the test generation summary message in the /test Tab before generating unit tests + private processChatSummaryMessage = async (messageData: any): Promise => { + if (this.onChatAnswerUpdated === undefined) { + return + } + if (messageData.message !== undefined) { + const answer: ChatItem = { + type: messageData.messageType, + messageId: messageData.messageId ?? messageData.triggerID, + body: messageData.message, + canBeVoted: true, + footer: messageData.filePath + ? { + fileList: { + rootFolderTitle: undefined, + fileTreeTitle: '', + filePaths: [messageData.filePath], + details: { + [messageData.filePath]: { + icon: MynahIcons.FILE, + description: `Generating tests in ${messageData.filePath}`, + }, + }, + }, + } + : {}, + } + this.onChatAnswerUpdated(messageData.tabID, answer) + } + } + + override processAuthNeededException = async (messageData: any): Promise => { + if (this.onChatAnswerReceived === undefined) { + return + } + + this.onChatAnswerReceived( + messageData.tabID, + { + type: ChatItemType.SYSTEM_PROMPT, + body: messageData.message, + }, + messageData + ) + } + + private processBuildProgressMessage = async ( + messageData: { type: TestMessageType } & Record + ): Promise => { + if (this.onChatAnswerReceived === undefined) { + return + } + const answer: ChatItem = { + type: messageData.messageType, + canBeVoted: messageData.canBeVoted, + messageId: messageData.messageId, + followUp: messageData.followUps, + fileList: messageData.fileList, + body: messageData.message, + codeReference: messageData.codeReference, + } + this.onChatAnswerReceived(messageData.tabID, answer, messageData) + } + + // This handles messages received from the extension, to be forwarded to the webview + handleMessageReceive = async (messageData: { type: TestMessageType } & Record) => { + switch (messageData.type) { + case 'authNeededException': + await this.processAuthNeededException(messageData) + break + case 'authenticationUpdateMessage': + this.onAuthenticationUpdate(messageData.testEnabled, messageData.authenticatingTabIDs) + break + case 'chatInputEnabledMessage': + this.chatInputEnabled(messageData.tabID, messageData.enabled) + break + case 'chatMessage': + await this.processChatMessage(messageData) + break + case 'chatSummaryMessage': + await this.processChatSummaryMessage(messageData) + break + case 'updatePlaceholderMessage': + this.updatePlaceholder(messageData.tabID, messageData.newPlaceholder) + break + case 'buildProgressMessage': + await this.processBuildProgressMessage(messageData) + break + case 'updatePromptProgress': + this.updatePromptProgress(messageData.tabID, messageData.progressField) + break + case 'errorMessage': + this.onError(messageData.tabID, messageData.message, messageData.title) + } + } +} diff --git a/packages/core/src/amazonq/webview/ui/commands.ts b/packages/core/src/amazonq/webview/ui/commands.ts index dafeca8a9fc..d668cb5d3b7 100644 --- a/packages/core/src/amazonq/webview/ui/commands.ts +++ b/packages/core/src/amazonq/webview/ui/commands.ts @@ -10,12 +10,15 @@ type MessageCommand = | 'tab-was-removed' | 'tab-was-changed' | 'ui-is-ready' + | 'disclaimer-acknowledged' | 'ui-focus' | 'follow-up-was-clicked' | 'auth-follow-up-was-clicked' | 'open-diff' | 'code_was_copied_to_clipboard' | 'insert_code_at_cursor_position' + | 'accept_diff' + | 'view_diff' | 'stop-response' | 'trigger-tabID-received' | 'clear' @@ -31,5 +34,13 @@ type MessageCommand = | 'file-click' | 'form-action-click' | 'open-settings' + | 'start-chat-message-telemetry' + | 'stop-chat-message-telemetry' + | 'store-code-result-message-id' + | 'start-test-gen' + | 'review' + | 'open-user-guide' + | 'send-telemetry' + | 'update-welcome-count' export type ExtensionMessage = Record & { command: MessageCommand } diff --git a/packages/core/src/amazonq/webview/ui/connector.ts b/packages/core/src/amazonq/webview/ui/connector.ts index 12cdad34893..a0ddb355d87 100644 --- a/packages/core/src/amazonq/webview/ui/connector.ts +++ b/packages/core/src/amazonq/webview/ui/connector.ts @@ -3,16 +3,29 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ChatItem, FeedbackPayload, Engagement, ChatItemAction } from '@aws/mynah-ui' +import { + ChatItem, + FeedbackPayload, + Engagement, + ChatItemAction, + CodeSelectionType, + ProgressField, + ReferenceTrackerInformation, + ChatPrompt, +} from '@aws/mynah-ui' import { Connector as CWChatConnector } from './apps/cwChatConnector' import { Connector as FeatureDevChatConnector } from './apps/featureDevChatConnector' import { Connector as AmazonQCommonsConnector } from './apps/amazonqCommonsConnector' import { Connector as GumbyChatConnector } from './apps/gumbyChatConnector' +import { Connector as ScanChatConnector } from './apps/scanChatConnector' +import { Connector as TestChatConnector } from './apps/testChatConnector' +import { Connector as docChatConnector } from './apps/docChatConnector' import { ExtensionMessage } from './commands' import { TabType, TabsStorage } from './storages/tabsStorage' import { WelcomeFollowupType } from './apps/amazonqCommonsConnector' import { AuthFollowUpType } from './followUps/generator' import { DiffTreeFileInfo } from './diffTree/types' +import { UserIntent } from '@amzn/codewhisperer-streaming' export interface CodeReference { licenseName?: string @@ -24,25 +37,38 @@ export interface CodeReference { } } +export interface UploadHistory { + [key: string]: { + uploadId: string + timestamp: number + tabId: string + filePaths: DiffTreeFileInfo[] + deletedFiles: DiffTreeFileInfo[] + } +} + export interface ChatPayload { chatMessage: string - traceId?: string // TODO: instrumented for cwc, not for gumby/featuredev. Remove the ? once we support all features chatCommand?: string } -export interface TracedChatItem extends ChatItem { +// Adding userIntent param by extending ChatItem to send userIntent as part of amazonq_interactWithMessage telemetry event +export interface CWCChatItem extends ChatItem { traceId?: string + userIntent?: UserIntent + codeBlockLanguage?: string } export interface ConnectorProps { sendMessageToExtension: (message: ExtensionMessage) => void onMessageReceived?: (tabID: string, messageData: any, needToShowAPIDocsTab: boolean) => void + onRunTestMessageReceived?: (tabID: string, showRunTestMessage: boolean) => void onChatAnswerUpdated?: (tabID: string, message: ChatItem) => void - onChatAnswerReceived?: (tabID: string, message: TracedChatItem) => void + onChatAnswerReceived?: (tabID: string, message: ChatItem, messageData: any) => void onWelcomeFollowUpClicked: (tabID: string, welcomeFollowUpType: WelcomeFollowupType) => void onAsyncEventProgress: (tabID: string, inProgress: boolean, message: string | undefined) => void onQuickHandlerCommand: (tabID: string, command?: string, eventId?: string) => void - onCWCContextCommandMessage: (message: TracedChatItem, command?: string) => string | undefined + onCWCContextCommandMessage: (message: ChatItem, command?: string) => string | undefined onOpenSettingsMessage: (tabID: string) => void onError: (tabID: string, message: string, title: string) => void onWarning: (tabID: string, message: string, title: string) => void @@ -50,13 +76,17 @@ export interface ConnectorProps { tabID: string, filePaths: DiffTreeFileInfo[], deletedFiles: DiffTreeFileInfo[], - messageId: string + messageId: string, + disableFileActions: boolean ) => void onUpdatePlaceholder: (tabID: string, newPlaceholder: string) => void + onUpdatePromptProgress: (tabID: string, progressField: ProgressField) => void onChatInputEnabled: (tabID: string, enabled: boolean) => void onUpdateAuthentication: (featureDevEnabled: boolean, authenticatingTabIDs: string[]) => void onNewTab: (tabType: TabType) => void onFileActionClick: (tabID: string, messageId: string, filePath: string, actionName: string) => void + handleCommand: (chatPrompt: ChatPrompt, tabId: string) => void + sendStaticMessages: (tabID: string, messages: ChatItem[]) => void tabsStorage: TabsStorage } @@ -66,6 +96,9 @@ export class Connector { private readonly cwChatConnector private readonly featureDevChatConnector private readonly gumbyChatConnector + private readonly scanChatConnector + private readonly testChatConnector + private readonly docChatConnector private readonly tabsStorage private readonly amazonqCommonsConnector: AmazonQCommonsConnector @@ -76,10 +109,16 @@ export class Connector { this.onMessageReceived = props.onMessageReceived this.cwChatConnector = new CWChatConnector(props as ConnectorProps) this.featureDevChatConnector = new FeatureDevChatConnector(props) + this.docChatConnector = new docChatConnector(props) this.gumbyChatConnector = new GumbyChatConnector(props) + this.scanChatConnector = new ScanChatConnector(props) + this.testChatConnector = new TestChatConnector(props) this.amazonqCommonsConnector = new AmazonQCommonsConnector({ sendMessageToExtension: this.sendMessageToExtension, onWelcomeFollowUpClicked: props.onWelcomeFollowUpClicked, + onNewTab: props.onNewTab, + handleCommand: props.handleCommand, + sendStaticMessages: props.sendStaticMessages, }) this.tabsStorage = props.tabsStorage } @@ -102,6 +141,15 @@ export class Connector { break case 'gumby': this.gumbyChatConnector.onResponseBodyLinkClick(tabID, messageId, link) + break + case 'review': + this.scanChatConnector.onResponseBodyLinkClick(tabID, messageId, link) + break + case 'testgen': + this.testChatConnector.onResponseBodyLinkClick(tabID, messageId, link) + break + case 'doc': + this.docChatConnector.onResponseBodyLinkClick(tabID, messageId, link) } } @@ -117,21 +165,25 @@ export class Connector { switch (this.tabsStorage.getTab(tabID)?.type) { case 'gumby': return this.gumbyChatConnector.requestAnswer(tabID, payload) + case 'testgen': + return this.testChatConnector.requestAnswer(tabID, payload) } } - requestGenerativeAIAnswer = (tabID: string, payload: ChatPayload): Promise => + requestGenerativeAIAnswer = (tabID: string, messageId: string, payload: ChatPayload): Promise => new Promise((resolve, reject) => { if (this.isUIReady) { switch (this.tabsStorage.getTab(tabID)?.type) { case 'featuredev': - return this.featureDevChatConnector.requestGenerativeAIAnswer(tabID, payload) + return this.featureDevChatConnector.requestGenerativeAIAnswer(tabID, messageId, payload) + case 'doc': + return this.docChatConnector.requestGenerativeAIAnswer(tabID, messageId, payload) default: - return this.cwChatConnector.requestGenerativeAIAnswer(tabID, payload) + return this.cwChatConnector.requestGenerativeAIAnswer(tabID, messageId, payload) } } else { return setTimeout(() => { - return this.requestGenerativeAIAnswer(tabID, payload) + return this.requestGenerativeAIAnswer(tabID, messageId, payload) }, 2000) } }) @@ -147,20 +199,45 @@ export class Connector { help = (tabID: string): void => { switch (this.tabsStorage.getTab(tabID)?.type) { case 'cwc': + /** + * TODO remove cwc helper and switch to the generic one + * that welcome uses + */ this.cwChatConnector.help(tabID) break + case 'welcome': + this.amazonqCommonsConnector.sendMessage(tabID, 'help') + break } } + startTestGen = (tabID: string, prompt: string): void => { + this.testChatConnector.startTestGen(tabID, prompt) + } + transform = (tabID: string): void => { this.gumbyChatConnector.transform(tabID) } + scans = (tabID: string): void => { + this.scanChatConnector.scan(tabID) + } + + onStopChatResponse = (tabID: string): void => { + switch (this.tabsStorage.getTab(tabID)?.type) { + case 'featuredev': + this.featureDevChatConnector.onStopChatResponse(tabID) + break + case 'cwc': + this.cwChatConnector.onStopChatResponse(tabID) + break + } + } + handleMessageReceive = async (message: MessageEvent): Promise => { if (message.data === undefined) { return } - // TODO: potential json parsing error exists. Need to determine the failing case. const messageData = JSON.parse(message.data) @@ -174,7 +251,18 @@ export class Connector { await this.featureDevChatConnector.handleMessageReceive(messageData) } else if (messageData.sender === 'gumbyChat') { await this.gumbyChatConnector.handleMessageReceive(messageData) + } else if (messageData.sender === 'scanChat') { + await this.scanChatConnector.handleMessageReceive(messageData) + } else if (messageData.sender === 'testChat') { + await this.testChatConnector.handleMessageReceive(messageData) + } else if (messageData.sender === 'docChat') { + await this.docChatConnector.handleMessageReceive(messageData) + } else if (messageData.sender === 'amazonqCore') { + await this.amazonqCommonsConnector.handleMessageReceive(messageData) } + + // Reset lastCommand after message is rendered. + this.tabsStorage.updateTabLastCommand(messageData.tabID, '') } onTabAdd = (tabID: string): void => { @@ -195,6 +283,12 @@ export class Connector { case 'gumby': this.gumbyChatConnector.onTabAdd(tabID) break + case 'review': + this.scanChatConnector.onTabAdd(tabID) + break + case 'testgen': + this.testChatConnector.onTabAdd(tabID) + break } } @@ -203,6 +297,12 @@ export class Connector { case 'featuredev': this.featureDevChatConnector.onTabOpen(tabID) break + case 'doc': + this.docChatConnector.onTabOpen(tabID) + break + case 'review': + this.scanChatConnector.onTabOpen(tabID) + break } } @@ -219,7 +319,9 @@ export class Connector { codeReference?: CodeReference[], eventId?: string, codeBlockIndex?: number, - totalCodeBlocks?: number + totalCodeBlocks?: number, + userIntent?: string, + codeBlockLanguage?: string ): void => { switch (this.tabsStorage.getTab(tabID)?.type) { case 'cwc': @@ -231,15 +333,89 @@ export class Connector { codeReference, eventId, codeBlockIndex, - totalCodeBlocks + totalCodeBlocks, + userIntent, + codeBlockLanguage ) break case 'featuredev': - this.featureDevChatConnector.onCodeInsertToCursorPosition(tabID, code, type, codeReference) + this.featureDevChatConnector.onCodeInsertToCursorPosition( + tabID, + messageId, + code, + type, + codeReference, + eventId, + codeBlockIndex, + totalCodeBlocks, + userIntent, + codeBlockLanguage + ) + break + case 'testgen': + this.testChatConnector.onCodeInsertToCursorPosition(tabID, messageId, code, type, codeReference) break } } + onAcceptDiff = ( + tabId: string, + messageId: string, + actionId: string, + data?: string, + code?: string, + type?: CodeSelectionType, + referenceTrackerInformation?: ReferenceTrackerInformation[], + eventId?: string, + codeBlockIndex?: number, + totalCodeBlocks?: number + ) => { + const tabType = this.tabsStorage.getTab(tabId)?.type + this.sendMessageToExtension({ + tabType, + tabID: tabId, + command: 'accept_diff', + messageId, + actionId, + data, + code, + type, + referenceTrackerInformation, + eventId, + codeBlockIndex, + totalCodeBlocks, + }) + } + + onViewDiff = ( + tabId: string, + messageId: string, + actionId: string, + data?: string, + code?: string, + type?: CodeSelectionType, + referenceTrackerInformation?: ReferenceTrackerInformation[], + eventId?: string, + codeBlockIndex?: number, + totalCodeBlocks?: number + ) => { + const tabType = this.tabsStorage.getTab(tabId)?.type + this.sendMessageToExtension({ + tabType, + tabID: tabId, + command: 'view_diff', + messageId, + actionId, + data, + code, + type, + referenceTrackerInformation, + eventId, + codeBlockIndex, + totalCodeBlocks, + }) + } + onCopyCodeToClipboard = ( tabID: string, messageId: string, @@ -248,7 +424,9 @@ export class Connector { codeReference?: CodeReference[], eventId?: string, codeBlockIndex?: number, - totalCodeBlocks?: number + totalCodeBlocks?: number, + userIntent?: string, + codeBlockLanguage?: string ): void => { switch (this.tabsStorage.getTab(tabID)?.type) { case 'cwc': @@ -260,11 +438,24 @@ export class Connector { codeReference, eventId, codeBlockIndex, - totalCodeBlocks + totalCodeBlocks, + userIntent, + codeBlockLanguage ) break case 'featuredev': - this.featureDevChatConnector.onCopyCodeToClipboard(tabID, code, type, codeReference) + this.featureDevChatConnector.onCopyCodeToClipboard( + tabID, + messageId, + code, + type, + codeReference, + eventId, + codeBlockIndex, + totalCodeBlocks, + userIntent, + codeBlockLanguage + ) break } } @@ -279,9 +470,18 @@ export class Connector { case 'featuredev': this.featureDevChatConnector.onTabRemove(tabID) break + case 'doc': + this.docChatConnector.onTabRemove(tabID) + break case 'gumby': this.gumbyChatConnector.onTabRemove(tabID) break + case 'review': + this.scanChatConnector.onTabRemove(tabID) + break + case 'testgen': + this.testChatConnector.onTabRemove(tabID) + break } } @@ -330,6 +530,7 @@ export class Connector { const tabType = this.tabsStorage.getTab(tabID)?.type switch (tabType) { case 'cwc': + case 'doc': case 'featuredev': this.amazonqCommonsConnector.authFollowUpClicked(tabID, tabType, authType) } @@ -344,7 +545,16 @@ export class Connector { this.amazonqCommonsConnector.followUpClicked(tabID, followUp) break case 'featuredev': - this.featureDevChatConnector.followUpClicked(tabID, followUp) + this.featureDevChatConnector.followUpClicked(tabID, messageId, followUp) + break + case 'testgen': + this.testChatConnector.followUpClicked(tabID, messageId, followUp) + break + case 'review': + this.scanChatConnector.followUpClicked(tabID, messageId, followUp) + break + case 'doc': + this.docChatConnector.followUpClicked(tabID, messageId, followUp) break default: this.cwChatConnector.followUpClicked(tabID, messageId, followUp) @@ -357,24 +567,25 @@ export class Connector { case 'featuredev': this.featureDevChatConnector.onFileActionClick(tabID, messageId, filePath, actionName) break - } - } - - onOpenDiff = (tabID: string, filePath: string, deleted: boolean): void => { - switch (this.tabsStorage.getTab(tabID)?.type) { - case 'featuredev': - this.featureDevChatConnector.onOpenDiff(tabID, filePath, deleted) + case 'doc': + this.docChatConnector.onFileActionClick(tabID, messageId, filePath, actionName) break } } - onStopChatResponse = (tabID: string): void => { + onFileClick = (tabID: string, filePath: string, deleted: boolean, messageId?: string): void => { switch (this.tabsStorage.getTab(tabID)?.type) { case 'featuredev': - this.featureDevChatConnector.onStopChatResponse(tabID) + this.featureDevChatConnector.onOpenDiff(tabID, filePath, deleted, messageId) break - case 'cwc': - this.cwChatConnector.onStopChatResponse(tabID) + case 'testgen': + this.testChatConnector.onFileDiff(tabID, filePath, deleted, messageId) + break + case 'review': + this.scanChatConnector.onFileClick(tabID, filePath, messageId) + break + case 'doc': + this.docChatConnector.onOpenDiff(tabID, filePath, deleted) break } } @@ -398,6 +609,12 @@ export class Connector { case 'featuredev': this.featureDevChatConnector.onChatItemVoted(tabId, messageId, vote) break + case 'review': + this.scanChatConnector.onChatItemVoted(tabId, messageId, vote) + break + case 'testgen': + this.testChatConnector.onChatItemVoted(tabId, messageId, vote) + break } } @@ -411,6 +628,15 @@ export class Connector { case 'gumby': this.gumbyChatConnector.onCustomFormAction(tabId, action) break + case 'testgen': + this.testChatConnector.onCustomFormAction(tabId, action) + break + case 'review': + this.scanChatConnector.onCustomFormAction(tabId, action) + break + case 'doc': + this.docChatConnector.onCustomFormAction(tabId, action) + break case 'cwc': if (action.id === `open-settings`) { this.sendMessageToExtension({ @@ -419,6 +645,11 @@ export class Connector { tabType: 'cwc', }) } + break + case 'agentWalkthrough': { + this.amazonqCommonsConnector.onCustomFormAction(tabId, action) + break + } } } } diff --git a/packages/core/src/amazonq/webview/ui/diffTree/actions.ts b/packages/core/src/amazonq/webview/ui/diffTree/actions.ts index 2ad507cf9fe..93fa02b37dd 100644 --- a/packages/core/src/amazonq/webview/ui/diffTree/actions.ts +++ b/packages/core/src/amazonq/webview/ui/diffTree/actions.ts @@ -6,14 +6,21 @@ import { MynahIcons } from '@aws/mynah-ui' import { FileNodeAction, TreeNodeDetails } from '@aws/mynah-ui/dist/static' import { DiffTreeFileInfo } from './types' +import { uiComponentsTexts } from '../texts/constants' export function getDetails(filePaths: DiffTreeFileInfo[]): Record { const details: Record = {} for (const filePath of filePaths) { - if (filePath.rejected) { + if (filePath.changeApplied) { + details[filePath.relativePath] = { + status: 'success', + label: uiComponentsTexts.changeAccepted, + icon: MynahIcons.OK, + } + } else if (filePath.rejected) { details[filePath.relativePath] = { status: 'error', - label: 'File rejected', + label: uiComponentsTexts.changeRejected, icon: MynahIcons.CANCEL_CIRCLE, } } @@ -24,23 +31,32 @@ export function getDetails(filePaths: DiffTreeFileInfo[]): Record { const actions: Record = {} for (const filePath of filePaths) { + if (filePath.changeApplied) { + continue + } switch (filePath.rejected) { case true: actions[filePath.relativePath] = [ { icon: MynahIcons.REVERT, name: 'revert-rejection', - description: 'Revert rejection', + description: uiComponentsTexts.revertRejection, }, ] break case false: actions[filePath.relativePath] = [ + { + icon: MynahIcons.OK, + status: 'success', + name: 'accept-change', + description: uiComponentsTexts.acceptChange, + }, { icon: MynahIcons.CANCEL_CIRCLE, status: 'error', name: 'reject-change', - description: 'Reject change', + description: uiComponentsTexts.rejectChange, }, ] break diff --git a/packages/core/src/amazonq/webview/ui/diffTree/types.ts b/packages/core/src/amazonq/webview/ui/diffTree/types.ts index 05864541e0d..ce095b11455 100644 --- a/packages/core/src/amazonq/webview/ui/diffTree/types.ts +++ b/packages/core/src/amazonq/webview/ui/diffTree/types.ts @@ -7,4 +7,5 @@ export type DiffTreeFileInfo = { zipFilePath: string relativePath: string rejected: boolean + changeApplied: boolean } diff --git a/packages/core/src/amazonq/webview/ui/followUps/generator.ts b/packages/core/src/amazonq/webview/ui/followUps/generator.ts index a653b6ef29a..cce5726398f 100644 --- a/packages/core/src/amazonq/webview/ui/followUps/generator.ts +++ b/packages/core/src/amazonq/webview/ui/followUps/generator.ts @@ -52,6 +52,22 @@ export class FollowUpGenerator { }, ], } + case 'doc': + return { + text: 'Select one of the following...', + options: [ + { + pillText: 'Create a README', + prompt: 'Create a README', + type: 'CreateDocumentation', + }, + { + pillText: 'Update an existing README', + prompt: 'Update an existing README', + type: 'UpdateDocumentation', + }, + ], + } default: return { text: 'Try Examples:', diff --git a/packages/core/src/amazonq/webview/ui/followUps/handler.ts b/packages/core/src/amazonq/webview/ui/followUps/handler.ts index 162be107c7c..4cb7c8530dc 100644 --- a/packages/core/src/amazonq/webview/ui/followUps/handler.ts +++ b/packages/core/src/amazonq/webview/ui/followUps/handler.ts @@ -3,11 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ChatItemAction, ChatItemType, MynahUI } from '@aws/mynah-ui' +import { ChatItemAction, ChatItemType, MynahIcons, MynahUI } from '@aws/mynah-ui' import { Connector } from '../connector' import { TabsStorage } from '../storages/tabsStorage' import { WelcomeFollowupType } from '../apps/amazonqCommonsConnector' import { AuthFollowUpType } from './generator' +import { FollowUpTypes } from '../../../commons/types' export interface FollowUpInteractionHandlerProps { mynahUI: MynahUI @@ -46,6 +47,7 @@ export class FollowUpInteractionHandler { if (followUp.prompt !== undefined) { this.mynahUI.updateStore(tabID, { loadingChat: true, + cancelButtonWhenLoading: false, promptInputDisabledState: true, }) this.mynahUI.addChatItem(tabID, { @@ -60,12 +62,89 @@ export class FollowUpInteractionHandler { this.tabsStorage.resetTabTimer(tabID) if (followUp.type !== undefined && followUp.type === 'init-prompt') { - void this.connector.requestGenerativeAIAnswer(tabID, { + void this.connector.requestGenerativeAIAnswer(tabID, messageId, { chatMessage: followUp.prompt, }) return } } + + const addChatItem = (tabID: string, messageId: string, options: any[]) => { + this.mynahUI.addChatItem(tabID, { + type: ChatItemType.ANSWER_PART, + messageId, + followUp: { + text: '', + options, + }, + }) + } + + const ViewDiffOptions = [ + { + icon: MynahIcons.OK, + pillText: 'Accept', + status: 'success', + type: FollowUpTypes.AcceptCode, + }, + { + icon: MynahIcons.REVERT, + pillText: 'Reject', + status: 'error', + type: FollowUpTypes.RejectCode, + }, + ] + + const AcceptCodeOptions = [ + { + icon: MynahIcons.OK, + pillText: 'Accepted', + status: 'success', + disabled: true, + }, + ] + + const RejectCodeOptions = [ + { + icon: MynahIcons.REVERT, + pillText: 'Rejected', + status: 'error', + disabled: true, + }, + ] + + const ViewCodeDiffAfterIterationOptions = [ + { + icon: MynahIcons.OK, + pillText: 'Accept', + status: 'success', + type: FollowUpTypes.AcceptCode, + }, + { + icon: MynahIcons.REVERT, + pillText: 'Reject', + status: 'error', + type: FollowUpTypes.RejectCode, // TODO: Add new Followup Action for "Reject" + }, + ] + + if (this.tabsStorage.getTab(tabID)?.type === 'testgen') { + switch (followUp.type) { + case FollowUpTypes.ViewDiff: + addChatItem(tabID, messageId, ViewDiffOptions) + break + case FollowUpTypes.AcceptCode: + addChatItem(tabID, messageId, AcceptCodeOptions) + break + case FollowUpTypes.RejectCode: + addChatItem(tabID, messageId, RejectCodeOptions) + break + case FollowUpTypes.ViewCodeDiffAfterIteration: + addChatItem(tabID, messageId, ViewCodeDiffAfterIterationOptions) + break + } + } + this.connector.onFollowUpClicked(tabID, messageId, followUp) } diff --git a/packages/core/src/amazonq/webview/ui/main.ts b/packages/core/src/amazonq/webview/ui/main.ts index 300988ac54b..78ae17aef95 100644 --- a/packages/core/src/amazonq/webview/ui/main.ts +++ b/packages/core/src/amazonq/webview/ui/main.ts @@ -2,8 +2,18 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -import { Connector, TracedChatItem } from './connector' -import { ChatItem, ChatItemType, MynahIcons, MynahUI, MynahUIDataModel, NotificationType } from '@aws/mynah-ui' +import { Connector, CWCChatItem } from './connector' +import { + ChatItem, + ChatItemType, + CodeSelectionType, + MynahIcons, + MynahUI, + MynahUIDataModel, + NotificationType, + ReferenceTrackerInformation, + ProgressField, +} from '@aws/mynah-ui' import { ChatPrompt } from '@aws/mynah-ui/dist/static' import { TabsStorage, TabType } from './storages/tabsStorage' import { WelcomeFollowupType } from './apps/amazonqCommonsConnector' @@ -16,12 +26,34 @@ import { TextMessageHandler } from './messages/handler' import { MessageController } from './messages/controller' import { getActions, getDetails } from './diffTree/actions' import { DiffTreeFileInfo } from './diffTree/types' +import { FeatureContext } from '../../../shared' +import { tryNewMap } from '../../util/functionUtils' +import { welcomeScreenTabData } from './walkthrough/welcome' +import { agentWalkthroughDataModel } from './walkthrough/agent' +import { createClickTelemetry, createOpenAgentTelemetry } from './telemetry/actions' +import { disclaimerAcknowledgeButtonId, disclaimerCard } from './texts/disclaimer' -export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { +/** + * The number of welcome chat tabs that can be opened before the NEXT one will become + * a regular chat tab. + */ +const welcomeCountThreshold = 3 + +export const createMynahUI = ( + ideApi: any, + amazonQEnabled: boolean, + featureConfigsSerialized: [string, FeatureContext][], + welcomeCount: number, + disclaimerAcknowledged: boolean, + disabledCommands?: string[] +) => { + let disclaimerCardActive = !disclaimerAcknowledged // eslint-disable-next-line prefer-const let mynahUI: MynahUI // eslint-disable-next-line prefer-const let connector: Connector + // Store the mapping between messageId and messageUserIntent for amazonq_interactWithMessage telemetry + const responseMetadata = new Map() window.addEventListener('error', (e) => { const { error, message } = e @@ -44,11 +76,23 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { }) }, }) + + const showWelcomePage = () => { + return welcomeCount < welcomeCountThreshold + } + + const updateWelcomeCount = () => { + ideApi.postMessage({ + command: 'update-welcome-count', + }) + welcomeCount += 1 + } + // Adding the first tab as CWC tab tabsStorage.addTab({ id: 'tab-1', status: 'free', - type: 'cwc', + type: showWelcomePage() ? 'welcome' : 'cwc', isSelected: true, }) @@ -57,9 +101,18 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { let isGumbyEnabled = amazonQEnabled + let isScanEnabled = amazonQEnabled + let isTestEnabled = amazonQEnabled + + let isDocEnabled = amazonQEnabled + let tabDataGenerator = new TabDataGenerator({ isFeatureDevEnabled, isGumbyEnabled, + isScanEnabled, + isTestEnabled, + isDocEnabled, + disabledCommands, }) // eslint-disable-next-line prefer-const @@ -71,12 +124,61 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { // eslint-disable-next-line prefer-const let messageController: MessageController + // @ts-ignore + let featureConfigs: Map = tryNewMap(featureConfigsSerialized) + + function getCodeBlockActions(messageData: any) { + // Show ViewDiff and AcceptDiff for allowedCommands in CWC + const isEnabled = featureConfigs.get('ViewDiffInChat')?.variation === 'TREATMENT' + const tab = tabsStorage.getTab(messageData?.tabID || '') + const allowedCommands = [ + 'aws.amazonq.refactorCode', + 'aws.amazonq.fixCode', + 'aws.amazonq.optimizeCode', + 'aws.amazonq.sendToPrompt', + ] + if (isEnabled && tab?.type === 'cwc' && allowedCommands.includes(tab.lastCommand || '')) { + return { + 'insert-to-cursor': undefined, + accept_diff: { + id: 'accept_diff', + label: 'Apply Diff', + icon: MynahIcons.OK_CIRCLED, + data: messageData, + }, + view_diff: { + id: 'view_diff', + label: 'View Diff', + icon: MynahIcons.EYE, + data: messageData, + }, + } + } + // Show only "Copy" option for codeblocks in Q Test Tab + if (tab?.type === 'testgen') { + return { + 'insert-to-cursor': undefined, + } + } + // Default will show "Copy" and "Insert at cursor" for codeblocks + return {} + } + // eslint-disable-next-line prefer-const connector = new Connector({ tabsStorage, + /** + * Proxy for allowing underlying common connectors to call quick action handlers + */ + handleCommand: (chatPrompt: ChatPrompt, tabId: string) => { + quickActionHandler.handle(chatPrompt, tabId) + }, onUpdateAuthentication: (isAmazonQEnabled: boolean, authenticatingTabIDs: string[]): void => { isFeatureDevEnabled = isAmazonQEnabled isGumbyEnabled = isAmazonQEnabled + isScanEnabled = isAmazonQEnabled + isTestEnabled = isAmazonQEnabled + isDocEnabled = isAmazonQEnabled quickActionHandler = new QuickActionHandler({ mynahUI, @@ -84,12 +186,23 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { tabsStorage, isFeatureDevEnabled, isGumbyEnabled, + isScanEnabled, + isTestEnabled, + isDocEnabled, + disabledCommands, }) tabDataGenerator = new TabDataGenerator({ isFeatureDevEnabled, isGumbyEnabled, + isScanEnabled, + isTestEnabled, + isDocEnabled, + disabledCommands, }) + + featureConfigs = tryNewMap(featureConfigsSerialized) + // Set the new defaults for the quick action commands in all tabs now that isFeatureDevEnabled was enabled/disabled for (const tab of tabsStorage.getTabs()) { mynahUI.updateStore(tab.id, { @@ -105,7 +218,11 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { body: 'Authentication successful. Connected to Amazon Q.', }) - if (tabsStorage.getTab(tabID)?.type === 'gumby') { + if ( + tabsStorage.getTab(tabID)?.type === 'gumby' || + tabsStorage.getTab(tabID)?.type === 'review' || + tabsStorage.getTab(tabID)?.type === 'testgen' + ) { mynahUI.updateStore(tabID, { promptInputDisabledState: false, }) @@ -115,6 +232,7 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { }, onFileActionClick: (tabID: string, messageId: string, filePath: string, actionName: string): void => {}, onQuickHandlerCommand: (tabID: string, command?: string, eventId?: string) => { + tabsStorage.updateTabLastCommand(tabID, command) if (command === 'aws.awsq.transform') { quickActionHandler.handle({ command: '/transform' }, tabID, eventId) } else if (command === 'aws.awsq.clearchat') { @@ -122,10 +240,24 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { } }, onCWCContextCommandMessage: (message: ChatItem, command?: string): string | undefined => { + const selectedTab = tabsStorage.getSelectedTab() + tabsStorage.updateTabLastCommand(selectedTab?.id || '', command || '') + if (command === 'aws.amazonq.sendToPrompt') { - return messageController.sendSelectedCodeToTab(message) + return messageController.sendSelectedCodeToTab(message, command) } else { - return messageController.sendMessageToTab(message, 'cwc') + const tabID = messageController.sendMessageToTab(message, 'cwc', command) + if (tabID && command) { + ideApi.postMessage(createOpenAgentTelemetry('cwc', 'right-click')) + ideApi.postMessage({ + command: 'start-chat-message-telemetry', + trigger: 'onContextCommand', + tabID, + tabType: 'cwc', + startTime: Date.now(), + }) + } + return tabID } }, onWelcomeFollowUpClicked: (tabID: string, welcomeFollowUpType: WelcomeFollowupType) => { @@ -136,16 +268,23 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { promptInputDisabledState: tabsStorage.isTabDead(tabID) || !enabled, }) }, + onUpdatePromptProgress(tabID: string, progressField: ProgressField) { + mynahUI.updateStore(tabID, { + promptInputProgress: progressField, + }) + }, onAsyncEventProgress: ( tabID: string, inProgress: boolean, message: string | undefined, - messageId: string | undefined = undefined + messageId: string | undefined = undefined, + enableStopAction: boolean = false ) => { if (inProgress) { mynahUI.updateStore(tabID, { loadingChat: true, promptInputDisabledState: true, + cancelButtonWhenLoading: enableStopAction, }) if (message && messageId) { @@ -181,15 +320,21 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { mynahUI.updateChatAnswerWithMessageId(tabID, item.messageId, { ...(item.body !== undefined ? { body: item.body } : {}), ...(item.buttons !== undefined ? { buttons: item.buttons } : {}), + ...(item.followUp !== undefined ? { followUp: item.followUp } : {}), + ...(item.footer !== undefined ? { footer: item.footer } : {}), + ...(item.canBeVoted !== undefined ? { canBeVoted: item.canBeVoted } : {}), }) } else { mynahUI.updateLastChatAnswer(tabID, { ...(item.body !== undefined ? { body: item.body } : {}), ...(item.buttons !== undefined ? { buttons: item.buttons } : {}), - } as ChatItem) + ...(item.followUp !== undefined ? { followUp: item.followUp } : {}), + ...(item.footer !== undefined ? { footer: item.footer } : {}), + ...(item.canBeVoted !== undefined ? { canBeVoted: item.canBeVoted } : {}), + }) } }, - onChatAnswerReceived: (tabID: string, item: TracedChatItem) => { + onChatAnswerReceived: (tabID: string, item: CWCChatItem, messageData: any) => { if (item.type === ChatItemType.ANSWER_PART || item.type === ChatItemType.CODE_RESULT) { mynahUI.updateLastChatAnswer(tabID, { ...(item.messageId !== undefined ? { messageId: item.messageId } : {}), @@ -197,9 +342,21 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { ...(item.codeReference !== undefined ? { codeReference: item.codeReference } : {}), ...(item.body !== undefined ? { body: item.body } : {}), ...(item.relatedContent !== undefined ? { relatedContent: item.relatedContent } : {}), - ...(item.type === ChatItemType.CODE_RESULT - ? { type: ChatItemType.CODE_RESULT, fileList: item.fileList } - : {}), + ...(item.followUp !== undefined ? { followUp: item.followUp } : {}), + ...(item.fileList !== undefined ? { fileList: item.fileList } : {}), + }) + if ( + item.messageId !== undefined && + item.userIntent !== undefined && + item.codeBlockLanguage !== undefined + ) { + responseMetadata.set(item.messageId, [item.userIntent, item.codeBlockLanguage]) + } + ideApi.postMessage({ + command: 'update-chat-message-telemetry', + tabID, + tabType: tabsStorage.getTab(tabID)?.type, + time: Date.now(), }) return } @@ -211,7 +368,11 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { item.formItems !== undefined || item.buttons !== undefined ) { - mynahUI.addChatItem(tabID, item) + mynahUI.addChatItem(tabID, { + ...item, + messageId: item.messageId, + codeBlockActions: getCodeBlockActions(messageData), + }) } if ( @@ -221,6 +382,7 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { ) { mynahUI.updateStore(tabID, { loadingChat: true, + cancelButtonWhenLoading: false, promptInputDisabledState: true, }) @@ -235,19 +397,22 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { }) tabsStorage.updateTabStatus(tabID, 'free') - if (item.traceId) { - /** - * We've received an answer for a traceId and this message has - * completed its round trip. Send that information back to - * VSCode so we can emit a round trip event - **/ - ideApi.postMessage({ - type: 'stopChatMessageTelemetry', - tabID, - traceId: item.traceId, - tabType: tabsStorage.getTab(tabID)?.type, - }) - } + /** + * We've received an answer for a tabID and this message has + * completed its round trip. Send that information back to + * VSCode so we can emit a round trip event + **/ + ideApi.postMessage({ + command: 'stop-chat-message-telemetry', + tabID, + tabType: tabsStorage.getTab(tabID)?.type, + time: Date.now(), + }) + } + }, + onRunTestMessageReceived: (tabID: string, shouldRunTestMessage: boolean) => { + if (shouldRunTestMessage) { + quickActionHandler.handle({ command: '/test' }, tabID) } }, onMessageReceived: (tabID: string, messageData: MynahUIDataModel) => { @@ -257,7 +422,8 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { tabID: string, filePaths: DiffTreeFileInfo[], deletedFiles: DiffTreeFileInfo[], - messageId: string + messageId: string, + disableFileActions: boolean ) => { const updateWith: Partial = { type: ChatItemType.ANSWER, @@ -265,8 +431,8 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { rootFolderTitle: 'Changes', filePaths: filePaths.map((i) => i.zipFilePath), deletedFiles: deletedFiles.map((i) => i.zipFilePath), - details: getDetails(filePaths), - actions: getActions([...filePaths, ...deletedFiles]), + details: getDetails([...filePaths, ...deletedFiles]), + actions: disableFileActions ? undefined : getActions([...filePaths, ...deletedFiles]), }, } mynahUI.updateChatAnswerWithMessageId(tabID, messageId, updateWith) @@ -286,7 +452,7 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { onError: (tabID: string, message: string, title: string) => { const answer: ChatItem = { type: ChatItemType.ANSWER, - body: `**${title}** + body: `**${title}** ${message}`, } @@ -340,6 +506,7 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { connector.onUpdateTabType(newTabID) mynahUI.updateStore(newTabID, tabDataGenerator.getTabData(tabType, true)) + featureConfigs = tryNewMap(featureConfigsSerialized) }, onOpenSettingsMessage(tabId: string) { mynahUI.addChatItem(tabId, { @@ -362,65 +529,192 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { }) return }, + /** + * Helps with sending static messages that don't need to be sent through to the + * VSCode side. E.g. help messages + */ + sendStaticMessages(tabID: string, messages: ChatItem[]) { + if (tabsStorage.getTab(tabID)?.type === 'welcome') { + // set the tab type to cwc since its the most general one + tabsStorage.updateTabTypeFromUnknown(tabID, 'cwc') + + // collapse the ui before adding the message + mynahUI.updateStore(tabID, { + tabHeaderDetails: void 0, + compactMode: false, + tabBackground: false, + promptInputText: '', + promptInputLabel: void 0, + chatItems: [], + tabTitle: 'Chat', + }) + } + + for (const message of messages) { + mynahUI.addChatItem(tabID, message) + } + }, }) mynahUI = new MynahUI({ onReady: connector.uiReady, onTabAdd: (tabID: string) => { + /** + * If the next tab opening will cross the welcome count threshold then + * update the next tabs defaults + */ + if (welcomeCount + 1 >= welcomeCountThreshold) { + tabsStorage.updateTabTypeFromUnknown(tabID, 'cwc') + mynahUI?.updateTabDefaults({ + store: { + ...tabDataGenerator.getTabData('cwc', true), + tabHeaderDetails: void 0, + compactMode: false, + tabBackground: false, + }, + }) + } else { + // we haven't reached the welcome count limit yet + updateWelcomeCount() + } + // If featureDev has changed availability inbetween the default store settings and now // make sure to show/hide it accordingly mynahUI.updateStore(tabID, { quickActionCommands: tabDataGenerator.quickActionsGenerator.generateForTab('unknown'), + ...(disclaimerCardActive ? { promptInputStickyCard: disclaimerCard } : {}), }) connector.onTabAdd(tabID) }, onTabRemove: connector.onTabRemove, onTabChange: connector.onTabChange, // TODO: update mynah-ui this type doesn't seem correct https://github.com/aws/mynah-ui/blob/3777a39eb534a91fd6b99d6cf421ce78ee5c7526/src/main.ts#L372 + onStopChatResponse: (tabID: string) => { + mynahUI.updateStore(tabID, { + loadingChat: false, + promptInputDisabledState: false, + }) + connector.onStopChatResponse(tabID) + }, onChatPrompt: (tabID: string, prompt: ChatPrompt, eventId: string | undefined) => { if ((prompt.prompt ?? '') === '' && (prompt.command ?? '') === '') { return } - if (tabsStorage.getTab(tabID)?.type === 'featuredev') { + const tabType = tabsStorage.getTab(tabID)?.type + if (tabType === 'featuredev') { mynahUI.addChatItem(tabID, { type: ChatItemType.ANSWER_STREAM, }) - } else if (tabsStorage.getTab(tabID)?.type === 'gumby') { + } else if (tabType === 'gumby') { connector.requestAnswer(tabID, { chatMessage: prompt.prompt ?? '', - traceId: eventId as string, }) return } + if (tabType === 'welcome') { + mynahUI.updateStore(tabID, { + tabHeaderDetails: void 0, + compactMode: false, + tabBackground: false, + promptInputText: '', + promptInputLabel: void 0, + chatItems: [], + }) + } + + // handler for the "/" agent commands if (prompt.command !== undefined && prompt.command.trim() !== '') { quickActionHandler.handle(prompt, tabID, eventId) + + const newTabType = tabsStorage.getSelectedTab()?.type + if (newTabType) { + ideApi.postMessage(createOpenAgentTelemetry(newTabType, 'quick-action')) + } return } /** - * When a user presses "enter" send an event that indicates - * we should start tracking the round trip time for this message - **/ - ideApi.postMessage({ - type: 'startChatMessageTelemetry', - trigger: 'onChatPrompt', - tabID, - traceId: eventId, - tabType: tabsStorage.getTab(tabID)?.type, - startTime: Date.now(), - }) + * Update the tab title if coming from the welcome page + * non cwc panels will have this updated automatically + */ + if (tabType === 'welcome') { + mynahUI.updateStore(tabID, { + tabTitle: tabDataGenerator.getTabData('cwc', false).tabTitle, + }) + } + // handler for the cwc panel textMessageHandler.handle(prompt, tabID, eventId as string) }, onVote: connector.onChatItemVoted, onInBodyButtonClicked: (tabId, messageId, action, eventId) => { - connector.onCustomFormAction(tabId, messageId, action, eventId) + switch (action.id) { + case disclaimerAcknowledgeButtonId: { + disclaimerCardActive = false + + // post message to tell VSCode that disclaimer is acknowledged + ideApi.postMessage({ + command: 'disclaimer-acknowledged', + }) + + // create telemetry + ideApi.postMessage(createClickTelemetry('amazonq-disclaimer-acknowledge-button')) + + // remove all disclaimer cards from all tabs + Object.keys(mynahUI.getAllTabs()).forEach((storeTabKey) => { + // eslint-disable-next-line unicorn/no-null + mynahUI.updateStore(storeTabKey, { promptInputStickyCard: null }) + }) + return + } + case 'quick-start': { + /** + * quick start is the action on the welcome page. When its + * clicked it collapses the view and puts it into regular + * "chat" which is cwc + */ + tabsStorage.updateTabTypeFromUnknown(tabId, 'cwc') + + // show quick start in the current tab instead of a new one + mynahUI.updateStore(tabId, { + tabHeaderDetails: undefined, + compactMode: false, + tabBackground: false, + promptInputText: '/', + promptInputLabel: undefined, + chatItems: [], + }) + + ideApi.postMessage(createClickTelemetry('amazonq-welcome-quick-start-button')) + return + } + case 'explore': { + const newTabId = mynahUI.updateStore('', agentWalkthroughDataModel) + if (newTabId === undefined) { + mynahUI.notify({ + content: uiComponentsTexts.noMoreTabsTooltip, + type: NotificationType.WARNING, + }) + return + } + tabsStorage.updateTabTypeFromUnknown(newTabId, 'agentWalkthrough') + ideApi.postMessage(createClickTelemetry('amazonq-welcome-explore-button')) + return + } + default: { + connector.onCustomFormAction(tabId, messageId, action, eventId) + return + } + } }, onCustomFormAction: (tabId, action, eventId) => { connector.onCustomFormAction(tabId, undefined, action, eventId) }, + onChatPromptProgressActionButtonClicked: (tabID, action) => { + connector.onCustomFormAction(tabID, undefined, action) + }, onSendFeedback: (tabId, feedbackPayload) => { connector.sendFeedback(tabId, feedbackPayload) mynahUI.notify({ @@ -429,7 +723,74 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { content: 'Thanks for your feedback.', }) }, - onCodeInsertToCursorPosition: connector.onCodeInsertToCursorPosition, + onCodeInsertToCursorPosition: ( + tabId, + messageId, + code, + type, + referenceTrackerInfo, + eventId, + codeBlockIndex, + totalCodeBlocks + ) => { + connector.onCodeInsertToCursorPosition( + tabId, + messageId, + code, + type, + referenceTrackerInfo, + eventId, + codeBlockIndex, + totalCodeBlocks, + responseMetadata.get(messageId)?.[0] ?? undefined, + responseMetadata.get(messageId)?.[1] ?? undefined + ) + }, + onCodeBlockActionClicked: ( + tabId: string, + messageId: string, + actionId: string, + data?: string, + code?: string, + type?: CodeSelectionType, + referenceTrackerInformation?: ReferenceTrackerInformation[], + eventId?: string, + codeBlockIndex?: number, + totalCodeBlocks?: number + ) => { + switch (actionId) { + case 'accept_diff': + connector.onAcceptDiff( + tabId, + messageId, + actionId, + data, + code, + type, + referenceTrackerInformation, + eventId, + codeBlockIndex, + totalCodeBlocks + ) + break + case 'view_diff': + connector.onViewDiff( + tabId, + messageId, + actionId, + data, + code, + type, + referenceTrackerInformation, + eventId, + codeBlockIndex, + totalCodeBlocks + ) + break + default: + break + } + }, onCopyCodeToClipboard: ( tabId, messageId, @@ -448,7 +809,9 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { referenceTrackerInfo, eventId, codeBlockIndex, - totalCodeBlocks + totalCodeBlocks, + responseMetadata.get(messageId)?.[0] ?? undefined, + responseMetadata.get(messageId)?.[1] ?? undefined ) mynahUI.notify({ type: NotificationType.SUCCESS, @@ -481,15 +844,22 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { onFileActionClick: async (tabID: string, messageId: string, filePath: string, actionName: string) => { connector.onFileActionClick(tabID, messageId, filePath, actionName) }, - onOpenDiff: connector.onOpenDiff, + onFileClick: connector.onFileClick, tabs: { 'tab-1': { isSelected: true, - store: tabDataGenerator.getTabData('cwc', true), + store: { + ...(showWelcomePage() + ? welcomeScreenTabData(tabDataGenerator).store + : tabDataGenerator.getTabData('cwc', true)), + ...(disclaimerCardActive ? { promptInputStickyCard: disclaimerCard } : {}), + }, }, }, defaults: { - store: tabDataGenerator.getTabData('cwc', true), + store: showWelcomePage() + ? welcomeScreenTabData(tabDataGenerator).store + : tabDataGenerator.getTabData('cwc', true), }, config: { maxTabs: 10, @@ -498,6 +868,14 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { }, }) + /** + * Update the welcome count if we've initially shown + * the welcome page + */ + if (showWelcomePage()) { + updateWelcomeCount() + } + followUpsInteractionHandler = new FollowUpInteractionHandler({ mynahUI, connector, @@ -509,6 +887,9 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { tabsStorage, isFeatureDevEnabled, isGumbyEnabled, + isScanEnabled, + isTestEnabled, + isDocEnabled, }) textMessageHandler = new TextMessageHandler({ mynahUI, @@ -521,6 +902,9 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => { tabsStorage, isFeatureDevEnabled, isGumbyEnabled, + isScanEnabled, + isTestEnabled, + isDocEnabled, }) return { diff --git a/packages/core/src/amazonq/webview/ui/messages/controller.ts b/packages/core/src/amazonq/webview/ui/messages/controller.ts index a63034c637a..df9e5454ce1 100644 --- a/packages/core/src/amazonq/webview/ui/messages/controller.ts +++ b/packages/core/src/amazonq/webview/ui/messages/controller.ts @@ -15,6 +15,10 @@ export interface MessageControllerProps { tabsStorage: TabsStorage isFeatureDevEnabled: boolean isGumbyEnabled: boolean + isScanEnabled: boolean + isTestEnabled: boolean + isDocEnabled: boolean + disabledCommands?: string[] } export class MessageController { @@ -30,12 +34,20 @@ export class MessageController { this.tabDataGenerator = new TabDataGenerator({ isFeatureDevEnabled: props.isFeatureDevEnabled, isGumbyEnabled: props.isGumbyEnabled, + isScanEnabled: props.isScanEnabled, + isTestEnabled: props.isTestEnabled, + isDocEnabled: props.isDocEnabled, + disabledCommands: props.disabledCommands, }) } - public sendSelectedCodeToTab(message: ChatItem): string | undefined { + public sendSelectedCodeToTab(message: ChatItem, command: string = ''): string | undefined { const selectedTab = { ...this.tabsStorage.getSelectedTab() } - if (selectedTab?.id === undefined || selectedTab?.type === 'featuredev') { + if ( + selectedTab?.id === undefined || + selectedTab?.type === undefined || + ['featuredev', 'gumby', 'review', 'testgen', 'doc'].includes(selectedTab.type) + ) { // Create a new tab if there's none const newTabID: string | undefined = this.mynahUI.updateStore( '', @@ -53,6 +65,7 @@ export class MessageController { type: 'cwc', status: 'free', isSelected: true, + lastCommand: command, }) selectedTab.id = newTabID } @@ -61,7 +74,7 @@ export class MessageController { return selectedTab.id } - public sendMessageToTab(message: ChatItem, tabType: TabType): string | undefined { + public sendMessageToTab(message: ChatItem, tabType: TabType, command: string = ''): string | undefined { const selectedTab = this.tabsStorage.getSelectedTab() if ( @@ -71,9 +84,11 @@ export class MessageController { ) { this.tabsStorage.updateTabStatus(selectedTab.id, 'busy') this.tabsStorage.updateTabTypeFromUnknown(selectedTab.id, tabType) + this.tabsStorage.updateTabLastCommand(selectedTab.id, command) this.mynahUI.updateStore(selectedTab.id, { loadingChat: true, + cancelButtonWhenLoading: false, promptInputDisabledState: true, }) this.mynahUI.addChatItem(selectedTab.id, message) @@ -96,6 +111,7 @@ export class MessageController { }) return undefined } else { + this.tabsStorage.updateTabLastCommand(newTabID, command) this.mynahUI.addChatItem(newTabID, message) this.mynahUI.addChatItem(newTabID, { type: ChatItemType.ANSWER_STREAM, @@ -104,6 +120,7 @@ export class MessageController { this.mynahUI.updateStore(newTabID, { loadingChat: true, + cancelButtonWhenLoading: false, promptInputDisabledState: true, }) @@ -114,6 +131,7 @@ export class MessageController { status: 'busy', isSelected: true, openInteractionType: 'contextMenu', + lastCommand: command, }) this.tabsStorage.updateTabTypeFromUnknown(newTabID, 'cwc') diff --git a/packages/core/src/amazonq/webview/ui/messages/handler.ts b/packages/core/src/amazonq/webview/ui/messages/handler.ts index 590b9c11aea..d85774d23f6 100644 --- a/packages/core/src/amazonq/webview/ui/messages/handler.ts +++ b/packages/core/src/amazonq/webview/ui/messages/handler.ts @@ -25,6 +25,7 @@ export class TextMessageHandler { } public handle(chatPrompt: ChatPrompt, tabID: string, eventID: string) { + this.tabsStorage.updateTabLastCommand(tabID, chatPrompt.command) this.tabsStorage.updateTabTypeFromUnknown(tabID, 'cwc') this.tabsStorage.resetTabTimer(tabID) this.connector.onUpdateTabType(tabID) @@ -35,16 +36,16 @@ export class TextMessageHandler { this.mynahUI.updateStore(tabID, { loadingChat: true, + cancelButtonWhenLoading: false, promptInputDisabledState: true, }) this.tabsStorage.updateTabStatus(tabID, 'busy') void this.connector - .requestGenerativeAIAnswer(tabID, { + .requestGenerativeAIAnswer(tabID, eventID, { chatMessage: chatPrompt.prompt ?? '', chatCommand: chatPrompt.command, - traceId: eventID, }) .then(() => {}) } diff --git a/packages/core/src/amazonq/webview/ui/quickActions/generator.ts b/packages/core/src/amazonq/webview/ui/quickActions/generator.ts index 4508c664d66..81513a3e143 100644 --- a/packages/core/src/amazonq/webview/ui/quickActions/generator.ts +++ b/packages/core/src/amazonq/webview/ui/quickActions/generator.ts @@ -5,61 +5,113 @@ import { QuickActionCommand, QuickActionCommandGroup } from '@aws/mynah-ui/dist/static' import { TabType } from '../storages/tabsStorage' +import { MynahIcons } from '@aws/mynah-ui' export interface QuickActionGeneratorProps { isFeatureDevEnabled: boolean isGumbyEnabled: boolean + isScanEnabled: boolean + isTestEnabled: boolean + isDocEnabled: boolean + disableCommands?: string[] } export class QuickActionGenerator { public isFeatureDevEnabled: boolean private isGumbyEnabled: boolean + private isScanEnabled: boolean + private isTestEnabled: boolean + private isDocEnabled: boolean + private disabledCommands: string[] constructor(props: QuickActionGeneratorProps) { this.isFeatureDevEnabled = props.isFeatureDevEnabled this.isGumbyEnabled = props.isGumbyEnabled + this.isScanEnabled = props.isScanEnabled + this.isTestEnabled = props.isTestEnabled + this.isDocEnabled = props.isDocEnabled + this.disabledCommands = props.disableCommands ?? [] } public generateForTab(tabType: TabType): QuickActionCommandGroup[] { + // agentWalkthrough is static and doesn't have any quick actions + if (tabType === 'agentWalkthrough') { + return [] + } + + // TODO: Update acc to UX const quickActionCommands = [ { + groupName: `Q Developer agentic capabilities`, commands: [ - ...(this.isFeatureDevEnabled + ...(this.isFeatureDevEnabled && !this.disabledCommands.includes('/dev') ? [ { command: '/dev', + icon: MynahIcons.CODE_BLOCK, placeholder: 'Describe your task or issue in as much detail as possible', - description: - 'Plan and implement new functionality across multiple files in your workspace.', + description: 'Generate code to make a change in your project', + }, + ] + : []), + ...(this.isTestEnabled && !this.disabledCommands.includes('/test') + ? [ + { + command: '/test', + icon: MynahIcons.CHECK_LIST, + placeholder: 'Specify a function(s) in the current file (optional)', + description: 'Generate unit tests (python & java) for selected code', + }, + ] + : []), + ...(this.isScanEnabled && !this.disabledCommands.includes('/review') + ? [ + { + command: '/review', + icon: MynahIcons.BUG, + description: 'Identify and fix code issues before committing', + }, + ] + : []), + ...(this.isDocEnabled && !this.disabledCommands.includes('/doc') + ? [ + { + command: '/doc', + icon: MynahIcons.FILE, + description: 'Generate documentation', }, ] : []), - ...(this.isGumbyEnabled + ...(this.isGumbyEnabled && !this.disabledCommands.includes('/transform') ? [ { command: '/transform', - description: 'Transform your Java 8 or 11 Maven project to Java 17', + description: 'Transform your Java project', + icon: MynahIcons.TRANSFORM, }, ] : []), ], }, { + groupName: 'Quick Actions', commands: [ { command: '/help', + icon: MynahIcons.HELP, description: 'Learn more about Amazon Q', }, { command: '/clear', + icon: MynahIcons.TRASH, description: 'Clear this session', }, ], }, - ] + ].filter((section) => section.commands.length > 0) const commandUnavailability: Record< - TabType, + Exclude, { description: string unavailableItems: string[] @@ -71,11 +123,27 @@ export class QuickActionGenerator { }, featuredev: { description: "This command isn't available in /dev", - unavailableItems: ['/dev', '/transform', '/help', '/clear'], + unavailableItems: ['/help', '/clear'], + }, + review: { + description: "This command isn't available in /review", + unavailableItems: ['/help', '/clear'], }, gumby: { description: "This command isn't available in /transform", - unavailableItems: ['/dev', '/transform'], + unavailableItems: ['/dev', '/test', '/doc', '/review', '/help', '/clear'], + }, + testgen: { + description: "This command isn't available in /test", + unavailableItems: ['/help', '/clear'], + }, + doc: { + description: "This command isn't available in /doc", + unavailableItems: ['/help', '/clear'], + }, + welcome: { + description: '', + unavailableItems: ['/clear'], }, unknown: { description: '', @@ -85,6 +153,7 @@ export class QuickActionGenerator { return quickActionCommands.map((commandGroup) => { return { + groupName: commandGroup.groupName, commands: commandGroup.commands.map((commandItem: QuickActionCommand) => { const commandNotAvailable = commandUnavailability[tabType].unavailableItems.includes( commandItem.command diff --git a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts index f149f331c5c..78ef3d0e7ec 100644 --- a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts +++ b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ChatItemType, ChatPrompt, MynahUI, NotificationType } from '@aws/mynah-ui' +import { ChatItemType, ChatPrompt, MynahUI, NotificationType, MynahIcons } from '@aws/mynah-ui' import { TabDataGenerator } from '../tabs/generator' import { Connector } from '../connector' -import { TabsStorage } from '../storages/tabsStorage' +import { TabsStorage, TabType } from '../storages/tabsStorage' import { uiComponentsTexts } from '../texts/constants' export interface QuickActionsHandlerProps { @@ -15,8 +15,20 @@ export interface QuickActionsHandlerProps { tabsStorage: TabsStorage isFeatureDevEnabled: boolean isGumbyEnabled: boolean + isScanEnabled: boolean + isTestEnabled: boolean + isDocEnabled: boolean + disabledCommands?: string[] } +export interface HandleCommandProps { + tabID: string + tabType: TabType + isEnabled: boolean + chatPrompt?: ChatPrompt + eventId?: string + taskName?: string +} export class QuickActionHandler { private mynahUI: MynahUI private connector: Connector @@ -24,24 +36,45 @@ export class QuickActionHandler { private tabDataGenerator: TabDataGenerator private isFeatureDevEnabled: boolean private isGumbyEnabled: boolean + private isScanEnabled: boolean + private isTestEnabled: boolean + private isDocEnabled: boolean constructor(props: QuickActionsHandlerProps) { this.mynahUI = props.mynahUI this.connector = props.connector this.tabsStorage = props.tabsStorage + this.isDocEnabled = props.isDocEnabled this.tabDataGenerator = new TabDataGenerator({ isFeatureDevEnabled: props.isFeatureDevEnabled, isGumbyEnabled: props.isGumbyEnabled, + isScanEnabled: props.isScanEnabled, + isTestEnabled: props.isTestEnabled, + isDocEnabled: props.isDocEnabled, + disabledCommands: props.disabledCommands, }) this.isFeatureDevEnabled = props.isFeatureDevEnabled this.isGumbyEnabled = props.isGumbyEnabled + this.isScanEnabled = props.isScanEnabled + this.isTestEnabled = props.isTestEnabled } + /** + * Handle commands + * Inside of the welcome page commands update the current tab + * Outside of the welcome page commands create new tabs + */ public handle(chatPrompt: ChatPrompt, tabID: string, eventId?: string) { this.tabsStorage.resetTabTimer(tabID) switch (chatPrompt.command) { case '/dev': - this.handleFeatureDevCommand(chatPrompt, tabID, 'Q - Dev') + this.handleCommand({ + chatPrompt, + tabID, + taskName: 'Q - Dev', + tabType: 'featuredev', + isEnabled: this.isFeatureDevEnabled, + }) break case '/help': this.handleHelpCommand(tabID) @@ -49,34 +82,50 @@ export class QuickActionHandler { case '/transform': this.handleGumbyCommand(tabID, eventId) break + case '/review': + this.handleScanCommand(tabID, eventId) + break + case '/test': + this.handleTestCommand(chatPrompt, tabID, eventId) + break + case '/doc': + this.handleCommand({ + chatPrompt, + tabID, + taskName: 'Q - Doc', + tabType: 'doc', + isEnabled: this.isDocEnabled, + }) + break case '/clear': this.handleClearCommand(tabID) break } } - private handleGumbyCommand(tabID: string, eventId: string | undefined) { - if (!this.isGumbyEnabled) { + private handleScanCommand(tabID: string, eventId: string | undefined) { + if (!this.isScanEnabled) { return } - - let gumbyTabId: string | undefined = undefined + let scanTabId: string | undefined = undefined this.tabsStorage.getTabs().forEach((tab) => { - if (tab.type === 'gumby') { - gumbyTabId = tab.id + if (tab.type === 'review') { + scanTabId = tab.id } }) - if (gumbyTabId !== undefined) { - this.mynahUI.selectTab(gumbyTabId, eventId || '') - this.connector.onTabChange(gumbyTabId) + if (scanTabId !== undefined) { + this.mynahUI.selectTab(scanTabId, eventId || '') + this.connector.onTabChange(scanTabId) + this.connector.scans(scanTabId) return } let affectedTabId: string | undefined = tabID - // if there is no gumby tab, open a new one - if (this.tabsStorage.getTab(affectedTabId)?.type !== 'unknown') { + // if there is no scan tab, open a new one + const currentTabType = this.tabsStorage.getTab(affectedTabId)?.type + if (currentTabType !== 'unknown' && currentTabType !== 'welcome') { affectedTabId = this.mynahUI.updateStore('', { loadingChat: true, }) @@ -89,7 +138,7 @@ export class QuickActionHandler { }) return } else { - this.tabsStorage.updateTabTypeFromUnknown(affectedTabId, 'gumby') + this.tabsStorage.updateTabTypeFromUnknown(affectedTabId, 'review') this.connector.onKnownTabOpen(affectedTabId) this.connector.onUpdateTabType(affectedTabId) @@ -98,41 +147,74 @@ export class QuickActionHandler { chatItems: [], }) - this.mynahUI.updateStore(affectedTabId, this.tabDataGenerator.getTabData('gumby', true, undefined)) + this.mynahUI.updateStore(affectedTabId, this.tabDataGenerator.getTabData('review', true, undefined)) // creating a new tab and printing some title // disable chat prompt this.mynahUI.updateStore(affectedTabId, { loadingChat: true, }) - - this.connector.transform(affectedTabId) + this.connector.scans(affectedTabId) } } - private handleClearCommand(tabID: string) { - this.mynahUI.updateStore(tabID, { - chatItems: [], - }) - this.connector.clearChat(tabID) - } + private handleTestCommand(chatPrompt: ChatPrompt, tabID: string, eventId: string | undefined) { + if (!this.isTestEnabled) { + return + } + const testTabId = this.tabsStorage.getTabs().find((tab) => tab.type === 'testgen')?.id + const realPromptText = chatPrompt.escapedPrompt?.trim() ?? '' - private handleHelpCommand(tabID: string) { - // User entered help action, so change the tab type to 'cwc' if it's an unknown tab - if (this.tabsStorage.getTab(tabID)?.type === 'unknown') { - this.tabsStorage.updateTabTypeFromUnknown(tabID, 'cwc') + if (testTabId !== undefined) { + this.mynahUI.selectTab(testTabId, eventId || '') + this.connector.onTabChange(testTabId) + this.connector.startTestGen(testTabId, realPromptText) + return } - this.connector.help(tabID) + let affectedTabId: string | undefined = tabID + // if there is no test tab, open a new one + const currentTabType = this.tabsStorage.getTab(affectedTabId)?.type + if (currentTabType !== 'unknown' && currentTabType !== 'welcome') { + affectedTabId = this.mynahUI.updateStore('', { + loadingChat: true, + }) + } + + if (affectedTabId === undefined) { + this.mynahUI.notify({ + content: uiComponentsTexts.noMoreTabsTooltip, + type: NotificationType.WARNING, + }) + return + } else { + this.tabsStorage.updateTabTypeFromUnknown(affectedTabId, 'testgen') + this.connector.onKnownTabOpen(affectedTabId) + this.connector.onUpdateTabType(affectedTabId) + + // reset chat history + this.mynahUI.updateStore(affectedTabId, { + chatItems: [], + }) + + // creating a new tab and printing some title + this.mynahUI.updateStore( + affectedTabId, + this.tabDataGenerator.getTabData('testgen', realPromptText === '', 'Q - Test') + ) + + this.connector.startTestGen(affectedTabId, realPromptText) + } } - private handleFeatureDevCommand(chatPrompt: ChatPrompt, tabID: string, taskName: string) { - if (!this.isFeatureDevEnabled) { + private handleCommand(props: HandleCommandProps) { + if (!props.isEnabled) { return } - let affectedTabId: string | undefined = tabID - const realPromptText = chatPrompt.escapedPrompt?.trim() ?? '' - if (this.tabsStorage.getTab(affectedTabId)?.type !== 'unknown') { + let affectedTabId: string | undefined = props.tabID + const realPromptText = props.chatPrompt?.escapedPrompt?.trim() ?? '' + const currentTabType = this.tabsStorage.getTab(affectedTabId)?.type + if (currentTabType !== 'unknown' && currentTabType !== 'welcome') { affectedTabId = this.mynahUI.updateStore('', {}) } if (affectedTabId === undefined) { @@ -142,31 +224,137 @@ export class QuickActionHandler { }) return } else { - this.tabsStorage.updateTabTypeFromUnknown(affectedTabId, 'featuredev') + this.tabsStorage.updateTabTypeFromUnknown(affectedTabId, props.tabType) this.connector.onKnownTabOpen(affectedTabId) this.connector.onUpdateTabType(affectedTabId) this.mynahUI.updateStore(affectedTabId, { chatItems: [] }) - this.mynahUI.updateStore( - affectedTabId, - this.tabDataGenerator.getTabData('featuredev', realPromptText === '', taskName) - ) + if (props.tabType === 'featuredev') { + this.mynahUI.updateStore( + affectedTabId, + this.tabDataGenerator.getTabData(props.tabType, false, props.taskName) + ) + } else { + this.mynahUI.updateStore( + affectedTabId, + this.tabDataGenerator.getTabData(props.tabType, realPromptText === '', props.taskName) + ) + } + + const addInformationCard = (tabId: string) => { + if (props.tabType === 'featuredev') { + this.mynahUI.addChatItem(tabId, { + type: ChatItemType.ANSWER, + informationCard: { + title: 'Feature development', + description: 'Amazon Q Developer Agent for Software Development', + icon: MynahIcons.BUG, + content: { + body: [ + 'After you provide a task, I will:', + '1. Generate code based on your description and the code in your workspace', + '2. Provide a list of suggestions for you to review and add to your workspace', + '3. If needed, iterate based on your feedback', + 'To learn more, visit the [user guide](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/software-dev.html)', + ].join('\n'), + }, + }, + }) + } + } if (realPromptText !== '') { this.mynahUI.addChatItem(affectedTabId, { type: ChatItemType.PROMPT, body: realPromptText, }) + addInformationCard(affectedTabId) this.mynahUI.updateStore(affectedTabId, { loadingChat: true, + cancelButtonWhenLoading: false, promptInputDisabledState: true, }) - void this.connector.requestGenerativeAIAnswer(affectedTabId, { + void this.connector.requestGenerativeAIAnswer(affectedTabId, '', { chatMessage: realPromptText, }) + } else { + addInformationCard(affectedTabId) } } } + + private handleGumbyCommand(tabID: string, eventId: string | undefined) { + if (!this.isGumbyEnabled) { + return + } + + let gumbyTabId: string | undefined = undefined + + this.tabsStorage.getTabs().forEach((tab) => { + if (tab.type === 'gumby') { + gumbyTabId = tab.id + } + }) + + if (gumbyTabId !== undefined) { + this.mynahUI.selectTab(gumbyTabId, eventId || '') + this.connector.onTabChange(gumbyTabId) + return + } + + let affectedTabId: string | undefined = tabID + // if there is no gumby tab, open a new one + const currentTabType = this.tabsStorage.getTab(affectedTabId)?.type + if (currentTabType !== 'unknown' && currentTabType !== 'welcome') { + affectedTabId = this.mynahUI.updateStore('', { + loadingChat: true, + cancelButtonWhenLoading: false, + }) + } + + if (affectedTabId === undefined) { + this.mynahUI.notify({ + content: uiComponentsTexts.noMoreTabsTooltip, + type: NotificationType.WARNING, + }) + return + } else { + this.tabsStorage.updateTabTypeFromUnknown(affectedTabId, 'gumby') + this.connector.onKnownTabOpen(affectedTabId) + this.connector.onUpdateTabType(affectedTabId) + + // reset chat history + this.mynahUI.updateStore(affectedTabId, { + chatItems: [], + }) + + this.mynahUI.updateStore(affectedTabId, this.tabDataGenerator.getTabData('gumby', true, undefined)) + + // disable chat prompt + this.mynahUI.updateStore(affectedTabId, { + loadingChat: true, + cancelButtonWhenLoading: false, + }) + + this.connector.transform(affectedTabId) + } + } + + private handleClearCommand(tabID: string) { + this.mynahUI.updateStore(tabID, { + chatItems: [], + }) + this.connector.clearChat(tabID) + } + + private handleHelpCommand(tabID: string) { + // User entered help action, so change the tab type to 'cwc' if it's an unknown tab + if (this.tabsStorage.getTab(tabID)?.type === 'unknown') { + this.tabsStorage.updateTabTypeFromUnknown(tabID, 'cwc') + } + + this.connector.help(tabID) + } } diff --git a/packages/core/src/amazonq/webview/ui/storages/tabsStorage.ts b/packages/core/src/amazonq/webview/ui/storages/tabsStorage.ts index c858169315b..f9a419fed96 100644 --- a/packages/core/src/amazonq/webview/ui/storages/tabsStorage.ts +++ b/packages/core/src/amazonq/webview/ui/storages/tabsStorage.ts @@ -4,7 +4,39 @@ */ export type TabStatus = 'free' | 'busy' | 'dead' -export type TabType = 'cwc' | 'featuredev' | 'gumby' | 'unknown' +const TabTypes = [ + 'cwc', + 'featuredev', + 'gumby', + 'review', + 'testgen', + 'doc', + 'agentWalkthrough', + 'welcome', + 'unknown', +] as const +export type TabType = (typeof TabTypes)[number] +export function isTabType(value: string): value is TabType { + return (TabTypes as readonly string[]).includes(value) +} + +export function getTabCommandFromTabType(tabType: TabType): string { + switch (tabType) { + case 'featuredev': + return '/dev' + case 'doc': + return '/doc' + case 'gumby': + return '/transform' + case 'review': + return '/review' + case 'testgen': + return '/test' + default: + return '' + } +} + export type TabOpenType = 'click' | 'contextMenu' | 'hotkeys' const TabTimeoutDuration = 172_800_000 // 48hrs @@ -14,6 +46,7 @@ export interface Tab { type: TabType isSelected: boolean openInteractionType?: TabOpenType + lastCommand?: string } export class TabsStorage { @@ -62,6 +95,18 @@ export class TabsStorage { return this.tabs.get(tabID)?.status === 'dead' } + public updateTabLastCommand(tabID: string, command?: string) { + if (command === undefined) { + return + } + const currentTabValue = this.tabs.get(tabID) + if (currentTabValue === undefined || currentTabValue.status === 'dead') { + return + } + currentTabValue.lastCommand = command + this.tabs.set(tabID, currentTabValue) + } + public updateTabStatus(tabID: string, tabStatus: TabStatus) { const currentTabValue = this.tabs.get(tabID) if (currentTabValue === undefined || currentTabValue.status === 'dead') { @@ -73,7 +118,10 @@ export class TabsStorage { public updateTabTypeFromUnknown(tabID: string, tabType: TabType) { const currentTabValue = this.tabs.get(tabID) - if (currentTabValue === undefined || currentTabValue.type !== 'unknown') { + if ( + currentTabValue === undefined || + (currentTabValue.type !== 'unknown' && currentTabValue.type !== 'welcome') + ) { return } diff --git a/packages/core/src/amazonq/webview/ui/tabs/constants.ts b/packages/core/src/amazonq/webview/ui/tabs/constants.ts index 0db6a69d379..efbf700b91d 100644 --- a/packages/core/src/amazonq/webview/ui/tabs/constants.ts +++ b/packages/core/src/amazonq/webview/ui/tabs/constants.ts @@ -3,11 +3,24 @@ * SPDX-License-Identifier: Apache-2.0 */ import { TabType } from '../storages/tabsStorage' +import { QuickActionCommandGroup } from '@aws/mynah-ui' +import { userGuideURL } from '../texts/constants' export type TabTypeData = { title: string placeholder: string welcome: string + contextCommands?: QuickActionCommandGroup[] +} + +const workspaceCommand: QuickActionCommandGroup = { + groupName: 'Mention code', + commands: [ + { + command: '@workspace', + description: 'Reference all code in workspace.', + }, + ], } const commonTabData: TabTypeData = { @@ -15,26 +28,45 @@ const commonTabData: TabTypeData = { placeholder: 'Ask a question or enter "/" for quick actions', welcome: `Hi, I'm Amazon Q. I can answer your software development questions. Ask me to explain, debug, or optimize your code. - You can enter \`/\` to see a list of quick actions. Add @workspace to beginning of your message to include your entire workspace as context.`, + You can enter \`/\` to see a list of quick actions. Add @workspace to the beginning of your message to include your entire workspace as context.`, + contextCommands: [workspaceCommand], } -export const TabTypeDataMap: Record = { +export const TabTypeDataMap: Record, TabTypeData> = { unknown: commonTabData, cwc: commonTabData, featuredev: { title: 'Q - Dev', placeholder: 'Describe your task or issue in as much detail as possible', - welcome: `Hi! I'm the Amazon Q Developer Agent for software development. - -I can generate code to implement new functionality across your workspace. To get started, describe the task you're trying to accomplish, and I'll generate code to implement it. If you want to make changes to the code, you can tell me what to improve and I'll generate new code based on your feedback. + welcome: `I can generate code to accomplish a task or resolve an issue. + +After you provide a description, I will: +1. Generate code based on your description and the code in your workspace +2. Provide a list of suggestions for you to review and add to your workspace +3. If needed, iterate based on your feedback -What would you like to work on?`, +To learn more, visit the [User Guide](${userGuideURL}).`, }, gumby: { title: 'Q - Code Transformation', placeholder: 'Open a new tab to chat with Q', - welcome: `Welcome to Code Transformation! + welcome: 'Welcome to Code Transformation!', + }, + review: { + title: 'Q - Review', + placeholder: `Ask a question or enter "/" for quick actions`, + welcome: `Welcome to code reviews. I can help you identify code issues and provide suggested fixes for the active file or workspace you have opened in your IDE.`, + }, + testgen: { + title: 'Q - Test', + placeholder: `Waiting on your inputs...`, + welcome: `Welcome to unit test generation. I can help you generate unit tests for your active file.`, + }, + doc: { + title: 'Q - Doc Generation', + placeholder: 'Ask Amazon Q to generate documentation for your project', + welcome: `Welcome to doc generation! -I can help you upgrade your Java 8 and 11 codebases to Java 17.`, +I can help generate documentation for your code. To get started, choose what type of doc update you'd like to make.`, }, } diff --git a/packages/core/src/amazonq/webview/ui/tabs/generator.ts b/packages/core/src/amazonq/webview/ui/tabs/generator.ts index b91e180da4e..b3263218c1d 100644 --- a/packages/core/src/amazonq/webview/ui/tabs/generator.ts +++ b/packages/core/src/amazonq/webview/ui/tabs/generator.ts @@ -8,10 +8,15 @@ import { TabType } from '../storages/tabsStorage' import { FollowUpGenerator } from '../followUps/generator' import { QuickActionGenerator } from '../quickActions/generator' import { TabTypeDataMap } from './constants' +import { agentWalkthroughDataModel } from '../walkthrough/agent' export interface TabDataGeneratorProps { isFeatureDevEnabled: boolean isGumbyEnabled: boolean + isScanEnabled: boolean + isTestEnabled: boolean + isDocEnabled: boolean + disabledCommands?: string[] } export class TabDataGenerator { @@ -23,27 +28,29 @@ export class TabDataGenerator { this.quickActionsGenerator = new QuickActionGenerator({ isFeatureDevEnabled: props.isFeatureDevEnabled, isGumbyEnabled: props.isGumbyEnabled, + isScanEnabled: props.isScanEnabled, + isTestEnabled: props.isTestEnabled, + isDocEnabled: props.isDocEnabled, + disableCommands: props.disabledCommands, }) } public getTabData(tabType: TabType, needWelcomeMessages: boolean, taskName?: string): MynahUIDataModel { + if (tabType === 'agentWalkthrough') { + return agentWalkthroughDataModel + } + + if (tabType === 'welcome') { + return {} + } + const tabData: MynahUIDataModel = { tabTitle: taskName ?? TabTypeDataMap[tabType].title, promptInputInfo: - 'Use of Amazon Q is subject to the [AWS Responsible AI Policy](https://aws.amazon.com/machine-learning/responsible-ai/policy/).', + 'Amazon Q Developer uses generative AI. You may need to verify responses. See the [AWS Responsible AI Policy](https://aws.amazon.com/machine-learning/responsible-ai/policy/).', quickActionCommands: this.quickActionsGenerator.generateForTab(tabType), promptInputPlaceholder: TabTypeDataMap[tabType].placeholder, - contextCommands: [ - { - groupName: 'Mention code', - commands: [ - { - command: '@workspace', - description: '(BETA) Reference all code in workspace.', - }, - ], - }, - ], + contextCommands: TabTypeDataMap[tabType].contextCommands, chatItems: needWelcomeMessages ? [ { diff --git a/packages/core/src/amazonq/webview/ui/telemetry/actions.ts b/packages/core/src/amazonq/webview/ui/telemetry/actions.ts new file mode 100644 index 00000000000..ffd65684ff6 --- /dev/null +++ b/packages/core/src/amazonq/webview/ui/telemetry/actions.ts @@ -0,0 +1,38 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ExtensionMessage } from '../commands' +import { TabType } from '../storages/tabsStorage' + +export function createClickTelemetry(source: string): ExtensionMessage { + return { + command: 'send-telemetry', + source, + } +} +export function isClickTelemetry(message: ExtensionMessage): boolean { + return ( + message.command === 'send-telemetry' && typeof message.source === 'string' && Object.keys(message).length === 2 + ) +} + +export function createOpenAgentTelemetry(module: TabType, trigger: Trigger): ExtensionMessage { + return { + command: 'send-telemetry', + module, + trigger, + } +} + +export type Trigger = 'right-click' | 'quick-action' | 'quick-start' + +export function isOpenAgentTelemetry(message: ExtensionMessage): boolean { + return ( + message.command === 'send-telemetry' && + typeof message.module === 'string' && + typeof message.trigger === 'string' && + Object.keys(message).length === 3 + ) +} diff --git a/packages/core/src/amazonq/webview/ui/texts/constants.ts b/packages/core/src/amazonq/webview/ui/texts/constants.ts index d0fb74469ee..d907308b8c3 100644 --- a/packages/core/src/amazonq/webview/ui/texts/constants.ts +++ b/packages/core/src/amazonq/webview/ui/texts/constants.ts @@ -19,13 +19,53 @@ export const uiComponentsTexts = { save: 'Save', cancel: 'Cancel', submit: 'Submit', - stopGenerating: 'Stop generating', + stopGenerating: 'Stop', copyToClipboard: 'Copied to clipboard', noMoreTabsTooltip: 'You can only open ten conversation tabs at a time.', codeSuggestionWithReferenceTitle: 'Some suggestions contain code with references.', spinnerText: 'Generating your answer...', + changeAccepted: 'Change accepted', + changeRejected: 'Change rejected', + acceptChange: 'Accept change', + rejectChange: 'Reject change', + revertRejection: 'Revert rejection', } - +export const docUserGuide = 'https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/doc-generation.html' export const userGuideURL = 'https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/software-dev.html' export const manageAccessGuideURL = 'https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security_iam_manage-access-with-policies.html' +export const testGuideUrl = 'https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/test-generation.html' +export const reviewGuideUrl = 'https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/code-reviews.html' + +export const helpMessage = `I'm Amazon Q, a generative AI assistant. Learn more about me below. Your feedback will help me improve. +\n\n### What I can do: +\n\n- Answer questions about AWS +\n\n- Answer questions about general programming concepts +\n\n- Answer questions about your workspace with @workspace +\n\n- Explain what a line of code or code function does +\n\n- Write unit tests and code +\n\n- Debug and fix code +\n\n- Refactor code +\n\n### What I don't do right now: +\n\n- Answer questions in languages other than English +\n\n- Remember conversations from your previous sessions +\n\n- Have information about your AWS account or your specific AWS resources +\n\n### Examples of questions I can answer: +\n\n- When should I use ElastiCache? +\n\n- How do I create an Application Load Balancer? +\n\n- Explain the and ask clarifying questions about it. +\n\n- What is the syntax of declaring a variable in TypeScript? +\n\n### Special Commands +\n\n- /dev - Get code suggestions across files in your current project. Provide a brief prompt, such as "Implement a GET API." +\n\n- /doc - Create and update documentation for your repository. +\n\n- /review - Discover and address security and code quality issues. +\n\n- /test - Generate unit tests for a file. +\n\n- /transform - Transform your code. Use to upgrade Java code versions. +\n\n- /help - View chat topics and commands. +\n\n- /clear - Clear the conversation. +\n\n### Things to note: +\n\n- I may not always provide completely accurate or current information. +\n\n- Provide feedback by choosing the like or dislike buttons that appear below answers. +\n\n- When you use Amazon Q, AWS may, for service improvement purposes, store data about your usage and content. You can opt-out of sharing this data by following the steps in AI services opt-out policies. See here +\n\n- Do not enter any confidential, sensitive, or personal information. +\n\n*For additional help, visit the [Amazon Q User Guide](${userGuideURL}).*` diff --git a/packages/core/src/amazonq/webview/ui/texts/disclaimer.ts b/packages/core/src/amazonq/webview/ui/texts/disclaimer.ts new file mode 100644 index 00000000000..fc666796272 --- /dev/null +++ b/packages/core/src/amazonq/webview/ui/texts/disclaimer.ts @@ -0,0 +1,20 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItem, MynahIcons } from '@aws/mynah-ui' + +export const disclaimerAcknowledgeButtonId = 'amazonq-disclaimer-acknowledge-button-id' +export const disclaimerCard: Partial = { + messageId: 'amazonq-disclaimer-card', + body: 'Amazon Q Developer uses generative AI. You may need to verify responses. See the [AWS Responsible AI Policy](https://aws.amazon.com/machine-learning/responsible-ai/policy/). Amazon Q Developer processes data across all US Regions. See [here](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/cross-region-inference.html) for more info. Amazon Q may retain chats to provide and maintain the service.', + buttons: [ + { + text: 'Acknowledge', + id: disclaimerAcknowledgeButtonId, + status: 'info', + icon: MynahIcons.OK, + }, + ], +} diff --git a/packages/core/src/amazonq/webview/ui/walkthrough/agent.ts b/packages/core/src/amazonq/webview/ui/walkthrough/agent.ts new file mode 100644 index 00000000000..f4a5add7aa1 --- /dev/null +++ b/packages/core/src/amazonq/webview/ui/walkthrough/agent.ts @@ -0,0 +1,201 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItemContent, ChatItemType, MynahIcons, MynahUIDataModel } from '@aws/mynah-ui' + +function createdTabbedData(examples: string[], agent: string): ChatItemContent['tabbedContent'] { + const exampleText = examples.map((example) => `- ${example}`).join('\n') + return [ + { + label: 'Examples', + value: 'examples', + content: { + body: `**Example use cases:**\n${exampleText}\n\nEnter ${agent} in Q Chat to get started`, + }, + }, + ] +} + +export const agentWalkthroughDataModel: MynahUIDataModel = { + tabBackground: false, + compactMode: false, + tabTitle: 'Explore', + promptInputVisible: false, + tabHeaderDetails: { + icon: MynahIcons.ASTERISK, + title: 'Amazon Q Developer agents capabilities', + description: '', + }, + chatItems: [ + { + type: ChatItemType.ANSWER, + snapToTop: true, + hoverEffect: true, + body: `### Feature development +Implement features or make changes across your workspace, all from a single prompt. +`, + icon: MynahIcons.CODE_BLOCK, + footer: { + tabbedContent: createdTabbedData( + [ + '/dev update app.py to add a new api', + '/dev fix the error', + '/dev add a new button to sort by ', + ], + '/dev' + ), + }, + buttons: [ + { + status: 'clear', + id: `user-guide-featuredev`, + disabled: false, + text: 'Read user guide', + }, + { + status: 'main', + disabled: false, + flash: 'once', + fillState: 'hover', + icon: MynahIcons.RIGHT_OPEN, + id: 'quick-start-featuredev', + text: `Quick start with **/dev**`, + }, + ], + }, + { + type: ChatItemType.ANSWER, + hoverEffect: true, + body: `### Unit test generation +Automatically generate unit tests for your active file. +`, + icon: MynahIcons.BUG, + footer: { + tabbedContent: createdTabbedData( + ['Generate tests for specific functions', 'Generate tests for null and empty inputs'], + '/test' + ), + }, + buttons: [ + { + status: 'clear', + id: 'user-guide-testgen', + disabled: false, + text: 'Read user guide', + }, + { + status: 'main', + disabled: false, + flash: 'once', + fillState: 'hover', + icon: MynahIcons.RIGHT_OPEN, + id: 'quick-start-testgen', + text: `Quick start with **/test**`, + }, + ], + }, + { + type: ChatItemType.ANSWER, + hoverEffect: true, + body: `### Documentation generation +Create and update READMEs for better documented code. +`, + icon: MynahIcons.CHECK_LIST, + footer: { + tabbedContent: createdTabbedData( + [ + 'Generate new READMEs for your project', + 'Update existing READMEs with recent code changes', + 'Request specific changes to a README', + ], + '/doc' + ), + }, + buttons: [ + { + status: 'clear', + id: 'user-guide-doc', + disabled: false, + text: 'Read user guide', + }, + { + status: 'main', + disabled: false, + flash: 'once', + fillState: 'hover', + icon: MynahIcons.RIGHT_OPEN, + id: 'quick-start-doc', + text: `Quick start with **/doc**`, + }, + ], + }, + { + type: ChatItemType.ANSWER, + hoverEffect: true, + body: `### Code reviews +Review code for issues, then get suggestions to fix your code instantaneously. +`, + icon: MynahIcons.TRANSFORM, + footer: { + tabbedContent: createdTabbedData( + [ + 'Review code for security vulnerabilities and code quality issues', + 'Get detailed explanations about code issues', + 'Apply automatic code fixes to your files', + ], + '/review' + ), + }, + buttons: [ + { + status: 'clear', + id: 'user-guide-review', + disabled: false, + text: 'Read user guide', + }, + { + status: 'main', + disabled: false, + flash: 'once', + fillState: 'hover', + icon: MynahIcons.RIGHT_OPEN, + id: 'quick-start-review', + text: `Quick start with **/review**`, + }, + ], + }, + { + type: ChatItemType.ANSWER, + hoverEffect: true, + body: `### Transformation +Upgrade library and language versions in your codebase. +`, + icon: MynahIcons.TRANSFORM, + footer: { + tabbedContent: createdTabbedData( + ['Upgrade Java language and dependency versions', 'Convert embedded SQL code in Java apps'], + '/transform' + ), + }, + buttons: [ + { + status: 'clear', + id: 'user-guide-gumby', + disabled: false, + text: 'Read user guide', + }, + { + status: 'main', + disabled: false, + flash: 'once', + fillState: 'hover', + icon: MynahIcons.RIGHT_OPEN, + id: 'quick-start-gumby', + text: `Quick start with **/transform**`, + }, + ], + }, + ], +} diff --git a/packages/core/src/amazonq/webview/ui/walkthrough/welcome.ts b/packages/core/src/amazonq/webview/ui/walkthrough/welcome.ts new file mode 100644 index 00000000000..a1df4bb8149 --- /dev/null +++ b/packages/core/src/amazonq/webview/ui/walkthrough/welcome.ts @@ -0,0 +1,47 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItemType, MynahIcons, MynahUITabStoreTab } from '@aws/mynah-ui' +import { TabDataGenerator } from '../tabs/generator' + +export const welcomeScreenTabData = (tabs: TabDataGenerator): MynahUITabStoreTab => ({ + isSelected: true, + store: { + quickActionCommands: tabs.quickActionsGenerator.generateForTab('welcome'), + contextCommands: tabs.getTabData('cwc', false).contextCommands, + tabTitle: 'Welcome to Q', + tabBackground: true, + chatItems: [ + { + type: ChatItemType.ANSWER, + icon: MynahIcons.ASTERISK, + messageId: 'new-welcome-card', + body: `#### Work on a task using agentic capabilities +_Generate code, scan for issues, and more._`, + buttons: [ + { + id: 'explore', + disabled: false, + text: 'Explore', + }, + { + id: 'quick-start', + text: 'Quick start', + disabled: false, + status: 'main', + }, + ], + }, + ], + promptInputLabel: 'Or, start a chat', + promptInputPlaceholder: 'Type your question', + compactMode: true, + tabHeaderDetails: { + title: "Hi, I'm Amazon Q.", + description: 'Where would you like to start?', + icon: MynahIcons.Q, + }, + }, +}) diff --git a/packages/core/src/amazonqDoc/app.ts b/packages/core/src/amazonqDoc/app.ts new file mode 100644 index 00000000000..4aba1b9e9bc --- /dev/null +++ b/packages/core/src/amazonqDoc/app.ts @@ -0,0 +1,101 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { ChatControllerEventEmitters, DocController } from './controllers/chat/controller' +import { AmazonQAppInitContext } from '../amazonq/apps/initContext' +import { MessagePublisher } from '../amazonq/messages/messagePublisher' +import { MessageListener } from '../amazonq/messages/messageListener' +import { fromQueryToParameters } from '../shared/utilities/uriUtils' +import { getLogger } from '../shared/logger' +import { AuthUtil } from '../codewhisperer/util/authUtil' +import { debounce } from 'lodash' +import { DocChatSessionStorage } from './storages/chatSession' +import { UIMessageListener } from './views/actions/uiMessageListener' +import globals from '../shared/extensionGlobals' +import { AppToWebViewMessageDispatcher } from '../amazonq/commons/connector/connectorMessages' +import { docChat, docScheme } from './constants' +import { TabIdNotFoundError } from '../amazonqFeatureDev/errors' +import { DocMessenger } from './messenger' + +export function init(appContext: AmazonQAppInitContext) { + const docChatControllerEventEmitters: ChatControllerEventEmitters = { + processHumanChatMessage: new vscode.EventEmitter(), + followUpClicked: new vscode.EventEmitter(), + openDiff: new vscode.EventEmitter(), + processChatItemVotedMessage: new vscode.EventEmitter(), + stopResponse: new vscode.EventEmitter(), + tabOpened: new vscode.EventEmitter(), + processChatItemFeedbackMessage: new vscode.EventEmitter(), + tabClosed: new vscode.EventEmitter(), + authClicked: new vscode.EventEmitter(), + formActionClicked: new vscode.EventEmitter(), + processResponseBodyLinkClick: new vscode.EventEmitter(), + insertCodeAtPositionClicked: new vscode.EventEmitter(), + fileClicked: new vscode.EventEmitter(), + } + + const messenger = new DocMessenger( + new AppToWebViewMessageDispatcher(appContext.getAppsToWebViewMessagePublisher()), + docChat + ) + const sessionStorage = new DocChatSessionStorage(messenger) + + new DocController( + docChatControllerEventEmitters, + messenger, + sessionStorage, + appContext.onDidChangeAmazonQVisibility.event + ) + + const docProvider = new (class implements vscode.TextDocumentContentProvider { + async provideTextDocumentContent(uri: vscode.Uri): Promise { + const params = fromQueryToParameters(uri.query) + + const tabID = params.get('tabID') + if (!tabID) { + getLogger().error(`Unable to find tabID from ${uri.toString()}`) + throw new TabIdNotFoundError() + } + + const session = await sessionStorage.getSession(tabID) + const content = await session.config.fs.readFile(uri) + const decodedContent = new TextDecoder().decode(content) + return decodedContent + } + })() + + const textDocumentProvider = vscode.workspace.registerTextDocumentContentProvider(docScheme, docProvider) + + globals.context.subscriptions.push(textDocumentProvider) + + const docChatUIInputEventEmitter = new vscode.EventEmitter() + + new UIMessageListener({ + chatControllerEventEmitters: docChatControllerEventEmitters, + webViewMessageListener: new MessageListener(docChatUIInputEventEmitter), + }) + + appContext.registerWebViewToAppMessagePublisher(new MessagePublisher(docChatUIInputEventEmitter), 'doc') + + const debouncedEvent = debounce(async () => { + const authenticated = (await AuthUtil.instance.getChatAuthState()).amazonQ === 'connected' + let authenticatingSessionIDs: string[] = [] + if (authenticated) { + const authenticatingSessions = sessionStorage.getAuthenticatingSessions() + + authenticatingSessionIDs = authenticatingSessions.map((session: any) => session.tabID) + + // We've already authenticated these sessions + authenticatingSessions.forEach((session: any) => (session.isAuthenticating = false)) + } + + messenger.sendAuthenticationUpdate(authenticated, authenticatingSessionIDs) + }, 500) + + AuthUtil.instance.secondaryAuth.onDidChangeActiveConnection(() => { + return debouncedEvent() + }) +} diff --git a/packages/core/src/amazonqDoc/constants.ts b/packages/core/src/amazonqDoc/constants.ts new file mode 100644 index 00000000000..ab872bd93e5 --- /dev/null +++ b/packages/core/src/amazonqDoc/constants.ts @@ -0,0 +1,121 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MynahIcons, Status } from '@aws/mynah-ui' +import { FollowUpTypes } from '../amazonq/commons/types' +import { NewFileInfo } from './types' +import { i18n } from '../shared/i18n-helper' + +// For uniquely identifiying which chat messages should be routed to Doc +export const docChat = 'docChat' + +export const docScheme = 'aws-doc' + +export const featureName = 'Amazon Q Doc Generation' + +export function getFileSummaryPercentage(input: string): number { + // Split the input string by newline characters + const lines = input.split('\n') + + // Find the line containing "summarized:" + const summaryLine = lines.find((line) => line.includes('summarized:')) + + // If the line is not found, return null + if (!summaryLine) { + return -1 + } + + // Extract the numbers from the summary line + const [summarized, total] = summaryLine.split(':')[1].trim().split(' of ').map(Number) + + // Calculate the percentage + const percentage = (summarized / total) * 100 + + return percentage +} + +const checkIcons = { + wait: '☐', + current: '☐', + done: '☑', +} + +const getIconForStep = (targetStep: number, currentStep: number) => { + return currentStep === targetStep + ? checkIcons.current + : currentStep > targetStep + ? checkIcons.done + : checkIcons.wait +} + +export enum DocGenerationStep { + UPLOAD_TO_S3, + SUMMARIZING_FILES, + GENERATING_ARTIFACTS, +} + +export const docGenerationProgressMessage = (currentStep: DocGenerationStep, mode: Mode) => ` +${mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.creating') : i18n('AWS.amazonq.doc.answer.updating')} + +${getIconForStep(DocGenerationStep.UPLOAD_TO_S3, currentStep)} ${i18n('AWS.amazonq.doc.answer.scanning')} + +${getIconForStep(DocGenerationStep.SUMMARIZING_FILES, currentStep)} ${i18n('AWS.amazonq.doc.answer.summarizing')} + +${getIconForStep(DocGenerationStep.GENERATING_ARTIFACTS, currentStep)} ${i18n('AWS.amazonq.doc.answer.generating')} + + +` + +export const FolderSelectorFollowUps = [ + { + icon: 'ok' as MynahIcons, + pillText: 'Yes', + prompt: 'Yes', + status: 'success' as Status, + type: FollowUpTypes.ProceedFolderSelection, + }, + { + icon: 'refresh' as MynahIcons, + pillText: 'Change folder', + prompt: 'Change folder', + status: 'info' as Status, + type: FollowUpTypes.ChooseFolder, + }, + { + icon: 'cancel' as MynahIcons, + pillText: 'Cancel', + prompt: 'Cancel', + status: 'error' as Status, + type: FollowUpTypes.CancelFolderSelection, + }, +] + +export const SynchronizeDocumentation = { + pillText: 'Update README with recent code changes', + prompt: 'Update README with recent code changes', + type: 'SynchronizeDocumentation', +} + +export const EditDocumentation = { + pillText: 'Make a specific change', + prompt: 'Make a specific change', + type: 'EditDocumentation', +} + +export enum Mode { + NONE = 'None', + CREATE = 'Create', + SYNC = 'Sync', + EDIT = 'Edit', +} + +/** + * + * @param paths file paths + * @returns the path to a README.md, or undefined if none exist + */ +export const findReadmePath = (paths?: NewFileInfo[]) => { + return paths?.find((path) => /readme\.md$/i.test(path.relativePath)) +} diff --git a/packages/core/src/amazonqDoc/controllers/chat/controller.ts b/packages/core/src/amazonqDoc/controllers/chat/controller.ts new file mode 100644 index 00000000000..40fcf037181 --- /dev/null +++ b/packages/core/src/amazonqDoc/controllers/chat/controller.ts @@ -0,0 +1,749 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import { EventEmitter } from 'vscode' + +import { + DocGenerationStep, + EditDocumentation, + FolderSelectorFollowUps, + Mode, + SynchronizeDocumentation, + docScheme, + featureName, + findReadmePath, +} from '../../constants' +import { AuthUtil } from '../../../codewhisperer/util/authUtil' +import { getLogger } from '../../../shared/logger' + +import { Session } from '../../session/session' +import { i18n } from '../../../shared/i18n-helper' +import { telemetry } from '../../../shared/telemetry' +import path from 'path' +import { createSingleFileDialog } from '../../../shared/ui/common/openDialog' +import { MynahIcons } from '@aws/mynah-ui' + +import { + MonthlyConversationLimitError, + SelectedFolderNotInWorkspaceFolderError, + WorkspaceFolderNotFoundError, + createUserFacingErrorMessage, +} from '../../../amazonqFeatureDev/errors' +import { BaseChatSessionStorage } from '../../../amazonq/commons/baseChatStorage' +import { DocMessenger } from '../../messenger' +import { AuthController } from '../../../amazonq/auth/controller' +import { openUrl } from '../../../shared/utilities/vsCodeUtils' +import { openDeletedDiff, openDiff } from '../../../amazonq/commons/diff' +import { + getWorkspaceFoldersByPrefixes, + getWorkspaceRelativePath, + isMultiRootWorkspace, +} from '../../../shared/utilities/workspaceUtils' +import { getPathsFromZipFilePath } from '../../../amazonqFeatureDev/util/files' +import { FollowUpTypes } from '../../../amazonq/commons/types' +import { DocGenerationTask } from '../docGenerationTask' + +export interface ChatControllerEventEmitters { + readonly processHumanChatMessage: EventEmitter + readonly followUpClicked: EventEmitter + readonly openDiff: EventEmitter + readonly stopResponse: EventEmitter + readonly tabOpened: EventEmitter + readonly tabClosed: EventEmitter + readonly processChatItemVotedMessage: EventEmitter + readonly processChatItemFeedbackMessage: EventEmitter + readonly authClicked: EventEmitter + readonly processResponseBodyLinkClick: EventEmitter + readonly insertCodeAtPositionClicked: EventEmitter + readonly fileClicked: EventEmitter + readonly formActionClicked: EventEmitter +} + +export class DocController { + private readonly scheme = docScheme + private readonly messenger: DocMessenger + private readonly sessionStorage: BaseChatSessionStorage + private authController: AuthController + private folderPath = '' + private mode: Mode = Mode.NONE + public docGenerationTask: DocGenerationTask + + public constructor( + private readonly chatControllerMessageListeners: ChatControllerEventEmitters, + messenger: DocMessenger, + sessionStorage: BaseChatSessionStorage, + _onDidChangeAmazonQVisibility: vscode.Event + ) { + this.messenger = messenger + this.sessionStorage = sessionStorage + this.authController = new AuthController() + this.docGenerationTask = new DocGenerationTask() + + this.chatControllerMessageListeners.processHumanChatMessage.event((data) => { + this.processUserChatMessage(data).catch((e) => { + getLogger().error('processUserChatMessage failed: %s', (e as Error).message) + }) + }) + this.chatControllerMessageListeners.formActionClicked.event((data) => { + return this.formActionClicked(data) + }) + + this.initializeFollowUps() + + this.chatControllerMessageListeners.stopResponse.event((data) => { + return this.stopResponse(data) + }) + this.chatControllerMessageListeners.tabOpened.event((data) => { + return this.tabOpened(data) + }) + this.chatControllerMessageListeners.tabClosed.event((data) => { + this.tabClosed(data) + }) + this.chatControllerMessageListeners.authClicked.event((data) => { + this.authClicked(data) + }) + this.chatControllerMessageListeners.processResponseBodyLinkClick.event((data) => { + this.processLink(data) + }) + this.chatControllerMessageListeners.fileClicked.event(async (data) => { + return await this.fileClicked(data) + }) + this.chatControllerMessageListeners.openDiff.event(async (data) => { + return await this.openDiff(data) + }) + } + + /** Prompts user to choose a folder in current workspace for README creation/update. + * After user chooses a folder, displays confirmation message to user with selected path. + * + */ + private async folderSelector(data: any) { + this.messenger.sendAnswer({ + type: 'answer', + tabID: data.tabID, + message: i18n('AWS.amazonq.doc.answer.chooseFolder'), + disableChatInput: true, + }) + + const uri = await createSingleFileDialog({ + canSelectFolders: true, + canSelectFiles: false, + }).prompt() + + const retryFollowUps = FolderSelectorFollowUps.filter( + (followUp) => followUp.type !== FollowUpTypes.ProceedFolderSelection + ) + + if (!(uri instanceof vscode.Uri)) { + this.messenger.sendAnswer({ + type: 'answer', + tabID: data.tabID, + message: i18n('AWS.amazonq.doc.error.noFolderSelected'), + followUps: retryFollowUps, + disableChatInput: true, + }) + // Check that selected folder is a subfolder of the current workspace + } else if (!vscode.workspace.getWorkspaceFolder(uri)) { + this.messenger.sendAnswer({ + type: 'answer', + tabID: data.tabID, + message: new SelectedFolderNotInWorkspaceFolderError().message, + followUps: retryFollowUps, + disableChatInput: true, + }) + } else { + let displayPath = '' + const relativePath = getWorkspaceRelativePath(uri.fsPath) + + if (relativePath) { + // Display path should always include workspace folder name + displayPath = path.join(relativePath.workspaceFolder.name, relativePath.relativePath) + // Only include workspace folder name in API call if multi-root workspace + this.folderPath = isMultiRootWorkspace() ? displayPath : relativePath.relativePath + + if (!relativePath.relativePath) { + this.docGenerationTask.folderLevel = 'ENTIRE_WORKSPACE' + } else { + this.docGenerationTask.folderLevel = 'SUB_FOLDER' + } + } + + this.messenger.sendFolderConfirmationMessage( + data.tabID, + this.mode === Mode.CREATE + ? i18n('AWS.amazonq.doc.answer.createReadme') + : i18n('AWS.amazonq.doc.answer.updateReadme'), + displayPath, + FolderSelectorFollowUps + ) + this.messenger.sendChatInputEnabled(data.tabID, false) + } + } + + private async openDiff(message: any) { + const tabId: string = message.tabID + const codeGenerationId: string = message.messageId + const zipFilePath: string = message.filePath + const session = await this.sessionStorage.getSession(tabId) + telemetry.amazonq_isReviewedChanges.emit({ + amazonqConversationId: session.conversationId, + enabled: true, + result: 'Succeeded', + credentialStartUrl: AuthUtil.instance.startUrl, + }) + + const workspacePrefixMapping = getWorkspaceFoldersByPrefixes(session.config.workspaceFolders) + const pathInfos = getPathsFromZipFilePath(zipFilePath, workspacePrefixMapping, session.config.workspaceFolders) + + const extension = path.parse(message.filePath).ext + // Only open diffs on files, not directories + if (extension) { + if (message.deleted) { + const name = path.basename(pathInfos.relativePath) + await openDeletedDiff(pathInfos.absolutePath, name, tabId, this.scheme) + } else { + let uploadId = session.uploadId + if (session?.state?.uploadHistory && session.state.uploadHistory[codeGenerationId]) { + uploadId = session?.state?.uploadHistory[codeGenerationId].uploadId + } + const rightPath = path.join(uploadId, zipFilePath) + await openDiff(pathInfos.absolutePath, rightPath, tabId, this.scheme) + } + } + } + + private initializeFollowUps(): void { + this.chatControllerMessageListeners.followUpClicked.event(async (data) => { + const session: Session = await this.sessionStorage.getSession(data.tabID) + + const workspaceFolders = vscode.workspace.workspaceFolders + if (workspaceFolders === undefined || workspaceFolders.length === 0) { + return + } + + const workspaceFolderName = vscode.workspace.workspaceFolders?.[0].name || '' + + const authState = await AuthUtil.instance.getChatAuthState() + + if (authState.amazonQ !== 'connected') { + await this.messenger.sendAuthNeededExceptionMessage(authState, data.tabID) + session.isAuthenticating = true + return + } + + this.docGenerationTask.userIdentity = AuthUtil.instance.conn?.id + + const sendFolderConfirmationMessage = (message: string) => { + this.messenger.sendFolderConfirmationMessage( + data.tabID, + message, + workspaceFolderName, + FolderSelectorFollowUps + ) + } + + switch (data.followUp.type) { + case FollowUpTypes.Retry: + if (this.mode === Mode.EDIT) { + this.enableUserInput(data?.tabID) + } else { + await this.tabOpened(data) + } + break + case FollowUpTypes.NewTask: + this.messenger.sendAnswer({ + type: 'answer', + tabID: data?.tabID, + message: i18n('AWS.amazonq.featureDev.answer.newTaskChanges'), + disableChatInput: true, + }) + return this.newTask(data) + case FollowUpTypes.CloseSession: + return this.closeSession(data) + case FollowUpTypes.CreateDocumentation: + this.docGenerationTask.interactionType = 'GENERATE_README' + this.mode = Mode.CREATE + sendFolderConfirmationMessage(i18n('AWS.amazonq.doc.answer.createReadme')) + break + case FollowUpTypes.ChooseFolder: + await this.folderSelector(data) + break + case FollowUpTypes.SynchronizeDocumentation: + this.mode = Mode.SYNC + sendFolderConfirmationMessage(i18n('AWS.amazonq.doc.answer.updateReadme')) + break + case FollowUpTypes.UpdateDocumentation: + this.docGenerationTask.interactionType = 'UPDATE_README' + this.messenger.sendAnswer({ + type: 'answer', + tabID: data?.tabID, + followUps: [SynchronizeDocumentation, EditDocumentation], + disableChatInput: true, + }) + break + case FollowUpTypes.EditDocumentation: + this.docGenerationTask.interactionType = 'EDIT_README' + this.mode = Mode.EDIT + sendFolderConfirmationMessage(i18n('AWS.amazonq.doc.answer.updateReadme')) + break + case FollowUpTypes.MakeChanges: + this.mode = Mode.EDIT + this.enableUserInput(data.tabID) + break + case FollowUpTypes.AcceptChanges: + this.docGenerationTask.userDecision = 'ACCEPT' + await this.sendDocGenerationEvent(data) + await this.insertCode(data) + return + case FollowUpTypes.RejectChanges: + this.docGenerationTask.userDecision = 'REJECT' + await this.sendDocGenerationEvent(data) + this.messenger.sendAnswer({ + type: 'answer', + tabID: data?.tabID, + disableChatInput: true, + message: 'Your changes have been discarded.', + followUps: [ + { + pillText: i18n('AWS.amazonq.featureDev.pillText.newTask'), + type: FollowUpTypes.NewTask, + status: 'info', + }, + { + pillText: i18n('AWS.amazonq.doc.pillText.closeSession'), + type: FollowUpTypes.CloseSession, + status: 'info', + }, + ], + }) + break + case FollowUpTypes.ProceedFolderSelection: + // If a user did not change the folder in a multi-root workspace, default to the first workspace folder + if (this.folderPath === '' && isMultiRootWorkspace()) { + this.folderPath = workspaceFolderName + } + if (this.mode === Mode.EDIT) { + this.enableUserInput(data.tabID) + } else { + await this.generateDocumentation({ + message: { + ...data, + message: + this.mode === Mode.CREATE + ? 'Create documentation for a specific folder' + : 'Sync documentation', + }, + session, + }) + } + break + case FollowUpTypes.CancelFolderSelection: + this.docGenerationTask.reset() + return this.tabOpened(data) + } + }) + } + + private enableUserInput(tabID: string) { + this.messenger.sendAnswer({ + type: 'answer', + tabID: tabID, + message: i18n('AWS.amazonq.doc.answer.editReadme'), + }) + this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.doc.placeholder.editReadme')) + this.messenger.sendChatInputEnabled(tabID, true) + } + + private async fileClicked(message: any) { + // TODO: add Telemetry here + const tabId: string = message.tabID + const messageId = message.messageId + const filePathToUpdate: string = message.filePath + + const session = await this.sessionStorage.getSession(tabId) + const filePathIndex = (session.state.filePaths ?? []).findIndex((obj) => obj.relativePath === filePathToUpdate) + if (filePathIndex !== -1 && session.state.filePaths) { + session.state.filePaths[filePathIndex].rejected = !session.state.filePaths[filePathIndex].rejected + } + const deletedFilePathIndex = (session.state.deletedFiles ?? []).findIndex( + (obj) => obj.relativePath === filePathToUpdate + ) + if (deletedFilePathIndex !== -1 && session.state.deletedFiles) { + session.state.deletedFiles[deletedFilePathIndex].rejected = + !session.state.deletedFiles[deletedFilePathIndex].rejected + } + + await session.updateFilesPaths( + tabId, + session.state.filePaths ?? [], + session.state.deletedFiles ?? [], + messageId, + true + ) + } + + private async formActionClicked(message: any) { + switch (message.action) { + case 'cancel-doc-generation': + // eslint-disable-next-line unicorn/no-null + await this.stopResponse(message) + + break + } + } + + private async newTask(message: any) { + // Old session for the tab is ending, delete it so we can create a new one for the message id + this.docGenerationTask = new DocGenerationTask() + const session = await this.sessionStorage.getSession(message.tabID) + telemetry.amazonq_endChat.emit({ + amazonqConversationId: session.conversationId, + amazonqEndOfTheConversationLatency: performance.now() - session.telemetry.sessionStartTime, + result: 'Succeeded', + }) + this.sessionStorage.deleteSession(message.tabID) + + // Re-run the opening flow, where we check auth + create a session + await this.tabOpened(message) + } + + private async closeSession(message: any) { + this.messenger.sendAnswer({ + type: 'answer', + tabID: message.tabID, + message: i18n('AWS.amazonq.featureDev.answer.sessionClosed'), + disableChatInput: true, + }) + this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.featureDev.placeholder.sessionClosed')) + this.messenger.sendChatInputEnabled(message.tabID, false) + + const session = await this.sessionStorage.getSession(message.tabID) + this.docGenerationTask.reset() + + telemetry.amazonq_endChat.emit({ + amazonqConversationId: session.conversationId, + amazonqEndOfTheConversationLatency: performance.now() - session.telemetry.sessionStartTime, + result: 'Succeeded', + }) + } + + private processErrorChatMessage = (err: any, message: any, session: Session | undefined) => { + const errorMessage = createUserFacingErrorMessage(`${err.cause?.message ?? err.message}`) + // eslint-disable-next-line unicorn/no-null + this.messenger.sendUpdatePromptProgress(message.tabID, null) + + switch (err.constructor.name) { + case MonthlyConversationLimitError.name: + this.messenger.sendMonthlyLimitError(message.tabID) + break + default: + this.messenger.sendErrorMessage(errorMessage, message.tabID, 0, session?.conversationIdUnsafe, false) + } + } + + private async generateDocumentation({ message, session }: { message: any; session: any }) { + try { + await this.onDocsGeneration(session, message.message, message.tabID) + } catch (err: any) { + this.processErrorChatMessage(err, message, session) + // Lock the chat input until they explicitly click one of the follow ups + this.messenger.sendChatInputEnabled(message.tabID, false) + } + } + + private async processUserChatMessage(message: any) { + if (message.message === undefined) { + this.messenger.sendErrorMessage('chatMessage should be set', message.tabID, 0, undefined) + return + } + + /** + * Don't attempt to process any chat messages when a workspace folder is not set. + * When the tab is first opened we will throw an error and lock the chat if the workspace + * folder is not found + */ + const workspaceFolders = vscode.workspace.workspaceFolders + if (workspaceFolders === undefined || workspaceFolders.length === 0) { + return + } + + const session: Session = await this.sessionStorage.getSession(message.tabID) + + try { + getLogger().debug(`${featureName}: Processing message: ${message.message}`) + + const authState = await AuthUtil.instance.getChatAuthState() + if (authState.amazonQ !== 'connected') { + await this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID) + session.isAuthenticating = true + return + } + + await this.generateDocumentation({ message, session }) + this.messenger.sendChatInputEnabled(message?.tabID, false) + this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.doc.pillText.selectOption')) + } catch (err: any) { + this.processErrorChatMessage(err, message, session) + // Lock the chat input until they explicitly click one of the follow ups + this.messenger.sendChatInputEnabled(message.tabID, false) + } + } + + private async stopResponse(message: any) { + telemetry.ui_click.emit({ elementId: 'amazonq_stopCodeGeneration' }) + this.messenger.sendAnswer({ + message: i18n('AWS.amazonq.featureDev.pillText.stoppingCodeGeneration'), + type: 'answer-part', + tabID: message.tabID, + }) + // eslint-disable-next-line unicorn/no-null + this.messenger.sendUpdatePromptProgress(message.tabID, null) + this.messenger.sendChatInputEnabled(message.tabID, false) + + const session = await this.sessionStorage.getSession(message.tabID) + session.state.tokenSource?.cancel() + } + + private async tabOpened(message: any) { + let session: Session | undefined + try { + session = await this.sessionStorage.getSession(message.tabID) + getLogger().debug(`${featureName}: Session created with id: ${session.tabID}`) + this.folderPath = '' + this.mode = Mode.NONE + + const authState = await AuthUtil.instance.getChatAuthState() + if (authState.amazonQ !== 'connected') { + void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID) + session.isAuthenticating = true + return + } + this.docGenerationTask.numberOfNavigation += 1 + this.messenger.sendAnswer({ + type: 'answer', + tabID: message.tabID, + followUps: [ + { + pillText: 'Create a README', + prompt: 'Create a README', + type: 'CreateDocumentation', + }, + { + pillText: 'Update an existing README', + prompt: 'Update an existing README', + type: 'UpdateDocumentation', + }, + ], + disableChatInput: true, + }) + this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.doc.pillText.selectOption')) + } catch (err: any) { + if (err instanceof WorkspaceFolderNotFoundError) { + this.messenger.sendAnswer({ + type: 'answer', + tabID: message.tabID, + message: err.message, + disableChatInput: true, + }) + } else { + this.messenger.sendErrorMessage( + createUserFacingErrorMessage(err.message), + message.tabID, + 0, + session?.conversationIdUnsafe + ) + } + } + } + + private async openMarkdownPreview(readmePath: vscode.Uri) { + await vscode.commands.executeCommand('vscode.open', readmePath) + await vscode.commands.executeCommand('markdown.showPreview') + } + + private async onDocsGeneration(session: Session, message: string, tabID: string) { + this.messenger.sendDocProgress(tabID, DocGenerationStep.UPLOAD_TO_S3, 0, this.mode) + + await session.preloader(message) + + try { + await session.send(message, this.mode, this.folderPath) + const filePaths = session.state.filePaths ?? [] + const deletedFiles = session.state.deletedFiles ?? [] + + // Only add the follow up accept/deny buttons when the tab hasn't been closed/request hasn't been cancelled + if (session?.state.tokenSource?.token.isCancellationRequested) { + return + } + + if (filePaths.length === 0 && deletedFiles.length === 0) { + this.messenger.sendAnswer({ + message: i18n('AWS.amazonq.featureDev.pillText.unableGenerateChanges'), + type: 'answer', + tabID: tabID, + canBeVoted: true, + disableChatInput: true, + }) + + return + } + + this.messenger.sendCodeResult( + filePaths, + deletedFiles, + session.state.references ?? [], + tabID, + session.uploadId, + session.state.codeGenerationId ?? '' + ) + + // Automatically open the README diff + const readmePath = findReadmePath(session.state.filePaths) + if (readmePath) { + await this.openDiff({ tabID, filePath: readmePath.zipFilePath }) + } + + const remainingIterations = session.state.codeGenerationRemainingIterationCount + const totalIterations = session.state.codeGenerationTotalIterationCount + + if (remainingIterations !== undefined && totalIterations !== undefined) { + this.messenger.sendAnswer({ + type: 'answer', + tabID: tabID, + message: `${this.mode === Mode.CREATE ? i18n('AWS.amazonq.doc.answer.readmeCreated') : i18n('AWS.amazonq.doc.answer.readmeUpdated')} ${i18n('AWS.amazonq.doc.answer.codeResult')}`, + disableChatInput: true, + }) + } + + this.messenger.sendAnswer({ + message: undefined, + type: 'system-prompt', + disableChatInput: true, + followUps: [ + { + pillText: 'Accept', + prompt: 'Accept', + type: FollowUpTypes.AcceptChanges, + icon: 'ok' as MynahIcons, + status: 'success', + }, + { + pillText: 'Make changes', + prompt: 'Make changes', + type: FollowUpTypes.MakeChanges, + icon: 'refresh' as MynahIcons, + status: 'info', + }, + { + pillText: 'Reject', + prompt: 'Reject', + type: FollowUpTypes.RejectChanges, + icon: 'cancel' as MynahIcons, + status: 'error', + }, + ], + tabID: tabID, + }) + } finally { + if (session?.state?.tokenSource?.token.isCancellationRequested) { + await this.newTask({ tabID }) + } else { + this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.doc.pillText.selectOption')) + + this.messenger.sendChatInputEnabled(tabID, false) + } + } + } + + private authClicked(message: any) { + this.authController.handleAuth(message.authType) + + this.messenger.sendAnswer({ + type: 'answer', + tabID: message.tabID, + message: 'Follow instructions to re-authenticate ...', + }) + + // Explicitly ensure the user goes through the re-authenticate flow + this.messenger.sendChatInputEnabled(message.tabID, false) + } + + private tabClosed(message: any) { + this.sessionStorage.deleteSession(message.tabID) + } + + private async insertCode(message: any) { + let session + try { + session = await this.sessionStorage.getSession(message.tabID) + + const acceptedFiles = (paths?: { rejected: boolean }[]) => (paths || []).filter((i) => !i.rejected).length + + const amazonqNumberOfFilesAccepted = + acceptedFiles(session.state.filePaths) + acceptedFiles(session.state.deletedFiles) + + telemetry.amazonq_isAcceptedCodeChanges.emit({ + credentialStartUrl: AuthUtil.instance.startUrl, + amazonqConversationId: session.conversationId, + amazonqNumberOfFilesAccepted, + enabled: true, + result: 'Succeeded', + }) + await session.insertChanges() + + const readmePath = findReadmePath(session.state.filePaths) + if (readmePath) { + await this.openMarkdownPreview( + vscode.Uri.file(path.join(readmePath.workspaceFolder.uri.fsPath, readmePath.relativePath)) + ) + } + + this.messenger.sendAnswer({ + type: 'answer', + disableChatInput: true, + tabID: message.tabID, + followUps: [ + { + pillText: 'Start a new documentation task', + prompt: 'Start a new documentation task', + type: FollowUpTypes.NewTask, + status: 'info', + }, + { + pillText: 'End session', + prompt: 'End session', + type: FollowUpTypes.CloseSession, + status: 'info', + }, + ], + }) + + this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.doc.pillText.selectOption')) + } catch (err: any) { + this.messenger.sendErrorMessage( + createUserFacingErrorMessage(`Failed to insert code changes: ${err.message}`), + message.tabID, + 0, + session?.conversationIdUnsafe + ) + } + } + private async sendDocGenerationEvent(message: any) { + const session = await this.sessionStorage.getSession(message.tabID) + this.docGenerationTask.conversationId = session.conversationId + const { totalAddedChars, totalAddedLines, totalAddedFiles } = await session.countAddedContent( + this.docGenerationTask.interactionType + ) + this.docGenerationTask.numberOfAddChars = totalAddedChars + this.docGenerationTask.numberOfAddLines = totalAddedLines + this.docGenerationTask.numberOfAddFiles = totalAddedFiles + const docGenerationEvent = this.docGenerationTask.docGenerationEventBase() + + await session.sendDocGenerationTelemetryEvent(docGenerationEvent) + } + private processLink(message: any) { + void openUrl(vscode.Uri.parse(message.link)) + } +} diff --git a/packages/core/src/amazonqDoc/controllers/docGenerationTask.ts b/packages/core/src/amazonqDoc/controllers/docGenerationTask.ts new file mode 100644 index 00000000000..848de727570 --- /dev/null +++ b/packages/core/src/amazonqDoc/controllers/docGenerationTask.ts @@ -0,0 +1,62 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { + DocGenerationEvent, + DocGenerationFolderLevel, + DocGenerationInteractionType, + DocGenerationUserDecision, +} from '../../codewhisperer/client/codewhispereruserclient' +import { getLogger } from '../../shared' + +export class DocGenerationTask { + // Telemetry fields + public conversationId?: string + public numberOfAddChars?: number + public numberOfAddLines?: number + public numberOfAddFiles?: number + public userDecision?: DocGenerationUserDecision + public interactionType?: DocGenerationInteractionType + public userIdentity?: string + public numberOfNavigation = 0 + public folderLevel: DocGenerationFolderLevel = 'ENTIRE_WORKSPACE' + + constructor(conversationId?: string) { + this.conversationId = conversationId + } + + public docGenerationEventBase() { + const undefinedProps = Object.entries(this) + .filter(([key, value]) => value === undefined) + .map(([key]) => key) + + if (undefinedProps.length > 0) { + getLogger().debug(`DocGenerationEvent has undefined properties: ${undefinedProps.join(', ')}`) + } + const event: DocGenerationEvent = { + conversationId: this.conversationId ?? '', + numberOfAddChars: this.numberOfAddChars, + numberOfAddLines: this.numberOfAddLines, + numberOfAddFiles: this.numberOfAddFiles, + userDecision: this.userDecision, + interactionType: this.interactionType, + userIdentity: this.userIdentity, + numberOfNavigation: this.numberOfNavigation, + folderLevel: this.folderLevel, + } + return event + } + + public reset() { + this.conversationId = undefined + this.numberOfAddChars = undefined + this.numberOfAddLines = undefined + this.numberOfAddFiles = undefined + this.userDecision = undefined + this.interactionType = undefined + this.userIdentity = undefined + this.numberOfNavigation = 0 + this.folderLevel = 'ENTIRE_WORKSPACE' + } +} diff --git a/packages/core/src/amazonqDoc/errors.ts b/packages/core/src/amazonqDoc/errors.ts new file mode 100644 index 00000000000..fb918ec7c53 --- /dev/null +++ b/packages/core/src/amazonqDoc/errors.ts @@ -0,0 +1,67 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ToolkitError } from '../shared/errors' +import { i18n } from '../shared/i18n-helper' + +export class DocServiceError extends ToolkitError { + constructor(message: string, code: string) { + super(message, { code }) + } +} + +export class ReadmeTooLargeError extends ToolkitError { + constructor() { + super(i18n('AWS.amazonq.doc.error.readmeTooLarge'), { + code: ReadmeTooLargeError.name, + }) + } +} + +export class WorkspaceEmptyError extends ToolkitError { + constructor() { + super(i18n('AWS.amazonq.doc.error.workspaceEmpty'), { + code: WorkspaceEmptyError.name, + }) + } +} + +export class NoChangeRequiredException extends ToolkitError { + constructor() { + super(i18n('AWS.amazonq.doc.error.noChangeRequiredException'), { + code: NoChangeRequiredException.name, + }) + } +} + +export class PromptRefusalException extends ToolkitError { + constructor() { + super(i18n('AWS.amazonq.doc.error.promptRefusal'), { + code: PromptRefusalException.name, + }) + } +} + +export class ContentLengthError extends ToolkitError { + constructor() { + super(i18n('AWS.amazonq.doc.error.contentLengthError'), { code: ContentLengthError.name }) + } +} + +export class PromptTooVagueError extends ToolkitError { + constructor() { + super(i18n('AWS.amazonq.doc.error.promptTooVague'), { + code: PromptTooVagueError.name, + }) + } +} + +export class PromptUnrelatedError extends ToolkitError { + constructor() { + super(i18n('AWS.amazonq.doc.error.promptUnrelated'), { + code: PromptUnrelatedError.name, + }) + } +} diff --git a/packages/core/src/amazonqDoc/index.ts b/packages/core/src/amazonqDoc/index.ts new file mode 100644 index 00000000000..7ba22e4b351 --- /dev/null +++ b/packages/core/src/amazonqDoc/index.ts @@ -0,0 +1,10 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './types' +export * from './session/sessionState' +export * from './constants' +export { Session } from './session/session' +export { ChatControllerEventEmitters, DocController } from './controllers/chat/controller' diff --git a/packages/core/src/amazonqDoc/messenger.ts b/packages/core/src/amazonqDoc/messenger.ts new file mode 100644 index 00000000000..09be3dd11fb --- /dev/null +++ b/packages/core/src/amazonqDoc/messenger.ts @@ -0,0 +1,72 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { Messenger } from '../amazonq/commons/connector/baseMessenger' +import { AppToWebViewMessageDispatcher } from '../amazonq/commons/connector/connectorMessages' +import { FollowUpTypes } from '../amazonq/commons/types' +import { messageWithConversationId } from '../amazonqFeatureDev' +import { i18n } from '../shared/i18n-helper' +import { docGenerationProgressMessage, DocGenerationStep, Mode } from './constants' +import { inProgress } from './types' + +export class DocMessenger extends Messenger { + public constructor(dispatcher: AppToWebViewMessageDispatcher, sender: string) { + super(dispatcher, sender) + } + + /** Sends a message in the chat and displays a prompt input progress bar to communicate the doc generation progress. + * The text in the progress bar matches the current step shown in the message. + * + */ + public sendDocProgress(tabID: string, step: DocGenerationStep, progress: number, mode: Mode) { + // Hide prompt input progress bar once all steps are completed + if (step > DocGenerationStep.GENERATING_ARTIFACTS) { + // eslint-disable-next-line unicorn/no-null + this.sendUpdatePromptProgress(tabID, null) + } else { + const progressText = + step === DocGenerationStep.UPLOAD_TO_S3 + ? `${i18n('AWS.amazonq.doc.answer.scanning')}...` + : step === DocGenerationStep.SUMMARIZING_FILES + ? `${i18n('AWS.amazonq.doc.answer.summarizing')}...` + : `${i18n('AWS.amazonq.doc.answer.generating')}...` + this.sendUpdatePromptProgress(tabID, inProgress(progress, progressText)) + } + + // The first step is answer-stream type, subequent updates are answer-part + this.sendAnswer({ + type: step === DocGenerationStep.UPLOAD_TO_S3 ? 'answer-stream' : 'answer-part', + tabID: tabID, + disableChatInput: true, + message: docGenerationProgressMessage(step, mode), + }) + } + + public override sendErrorMessage( + errorMessage: string, + tabID: string, + _retries: number, + conversationId?: string, + _showDefaultMessage?: boolean + ) { + this.sendAnswer({ + type: 'answer', + tabID: tabID, + message: errorMessage + messageWithConversationId(conversationId), + }) + + this.sendAnswer({ + message: undefined, + type: 'system-prompt', + followUps: [ + { + pillText: i18n('AWS.amazonq.featureDev.pillText.retry'), + type: FollowUpTypes.Retry, + status: 'warning', + }, + ], + tabID, + }) + } +} diff --git a/packages/core/src/amazonqDoc/session/session.ts b/packages/core/src/amazonqDoc/session/session.ts new file mode 100644 index 00000000000..309a6fd7408 --- /dev/null +++ b/packages/core/src/amazonqDoc/session/session.ts @@ -0,0 +1,268 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { featureName, Mode } from '../constants' +import { DeletedFileInfo, Interaction, NewFileInfo, SessionState, SessionStateConfig } from '../types' +import { PrepareCodeGenState } from './sessionState' +import { telemetry } from '../../shared/telemetry/telemetry' +import { extensionVersion, fs, getLogger, globals } from '../../shared' +import { AuthUtil } from '../../codewhisperer/util/authUtil' +import { SessionConfig } from '../../amazonq/commons/session/sessionConfigFactory' +import { ReferenceLogViewProvider } from '../../codewhisperer' +import path from 'path' +import { FeatureDevClient } from '../../amazonqFeatureDev/client/featureDev' +import { TelemetryHelper } from '../../amazonqFeatureDev/util/telemetryHelper' +import { ConversationNotStartedState } from '../../amazonqFeatureDev/session/sessionState' +import { logWithConversationId } from '../../amazonqFeatureDev/userFacingText' +import { ConversationIdNotFoundError } from '../../amazonqFeatureDev/errors' +import { referenceLogText } from '../../amazonqFeatureDev/constants' +import { + DocGenerationEvent, + DocGenerationInteractionType, + SendTelemetryEventRequest, +} from '../../codewhisperer/client/codewhispereruserclient' +import { getDiffCharsAndLines } from '../../shared/utilities/diffUtils' +import { getClientId, getOperatingSystem, getOptOutPreference } from '../../shared/telemetry/util' +import { DocMessenger } from '../messenger' + +export class Session { + private _state?: SessionState | Omit + private task: string = '' + private proxyClient: FeatureDevClient + private _conversationId?: string + private preloaderFinished = false + private _latestMessage: string = '' + private _telemetry: TelemetryHelper + + // Used to keep track of whether or not the current session is currently authenticating/needs authenticating + public isAuthenticating: boolean + + constructor( + public readonly config: SessionConfig, + private messenger: DocMessenger, + public readonly tabID: string, + initialState: Omit = new ConversationNotStartedState(tabID), + proxyClient: FeatureDevClient = new FeatureDevClient() + ) { + this._state = initialState + this.proxyClient = proxyClient + + this._telemetry = new TelemetryHelper() + this.isAuthenticating = false + } + + /** + * Preload any events that have to run before a chat message can be sent + */ + async preloader(msg: string) { + if (!this.preloaderFinished) { + await this.setupConversation(msg) + this.preloaderFinished = true + } + } + + get state() { + if (!this._state) { + throw new Error("State should be initialized before it's read") + } + return this._state + } + + /** + * setupConversation + * + * Starts a conversation with the backend and uploads the repo for the LLMs to be able to use it. + */ + private async setupConversation(msg: string) { + // Store the initial message when setting up the conversation so that if it fails we can retry with this message + this._latestMessage = msg + + await telemetry.amazonq_startConversationInvoke.run(async (span) => { + this._conversationId = await this.proxyClient.createConversation() + getLogger().info(logWithConversationId(this.conversationId)) + + span.record({ amazonqConversationId: this._conversationId, credentialStartUrl: AuthUtil.instance.startUrl }) + }) + + this._state = new PrepareCodeGenState( + { + ...this.getSessionStateConfig(), + conversationId: this.conversationId, + uploadId: '', + currentCodeGenerationId: undefined, + }, + [], + [], + [], + this.tabID, + 0 + ) + } + + private getSessionStateConfig(): Omit { + return { + workspaceRoots: this.config.workspaceRoots, + workspaceFolders: this.config.workspaceFolders, + proxyClient: this.proxyClient, + conversationId: this.conversationId, + } + } + + async send(msg: string, mode: Mode, folderPath?: string): Promise { + // When the task/"thing to do" hasn't been set yet, we want it to be the incoming message + if (this.task === '' && msg) { + this.task = msg + } + + this._latestMessage = msg + + return this.nextInteraction(msg, mode, folderPath) + } + private async nextInteraction(msg: string, mode: Mode, folderPath?: string) { + const resp = await this.state.interact({ + task: this.task, + msg, + fs: this.config.fs, + mode: mode, + folderPath: folderPath, + messenger: this.messenger, + telemetry: this.telemetry, + tokenSource: this.state.tokenSource, + uploadHistory: this.state.uploadHistory, + }) + + if (resp.nextState) { + if (!this.state?.tokenSource?.token.isCancellationRequested) { + this.state?.tokenSource?.cancel() + } + + // Move to the next state + this._state = resp.nextState + } + + return resp.interaction + } + + public async updateFilesPaths( + tabID: string, + filePaths: NewFileInfo[], + deletedFiles: DeletedFileInfo[], + messageId: string, + disableFileActions: boolean + ) { + this.messenger.updateFileComponent(tabID, filePaths, deletedFiles, messageId, disableFileActions) + } + + public async insertChanges() { + for (const filePath of this.state.filePaths?.filter((i) => !i.rejected) ?? []) { + const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath) + + const uri = filePath.virtualMemoryUri + const content = await this.config.fs.readFile(uri) + const decodedContent = new TextDecoder().decode(content) + + await fs.mkdir(path.dirname(absolutePath)) + await fs.writeFile(absolutePath, decodedContent) + } + + for (const filePath of this.state.deletedFiles?.filter((i) => !i.rejected) ?? []) { + const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath) + await fs.delete(absolutePath) + } + + for (const ref of this.state.references ?? []) { + ReferenceLogViewProvider.instance.addReferenceLog(referenceLogText(ref)) + } + } + + public async countAddedContent(interactionType?: DocGenerationInteractionType) { + let totalAddedChars = 0 + let totalAddedLines = 0 + let totalAddedFiles = 0 + + for (const filePath of this.state.filePaths?.filter((i) => !i.rejected) ?? []) { + const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath) + const uri = filePath.virtualMemoryUri + const content = await this.config.fs.readFile(uri) + const decodedContent = new TextDecoder().decode(content) + totalAddedFiles += 1 + + if ((await fs.exists(absolutePath)) && interactionType === 'UPDATE_README') { + const existingContent = await fs.readFileText(absolutePath) + const { addedChars, addedLines } = getDiffCharsAndLines(existingContent, decodedContent) + totalAddedChars += addedChars + totalAddedLines += addedLines + } else { + totalAddedChars += decodedContent.length + totalAddedLines += decodedContent.split('\n').length + } + } + + return { + totalAddedChars, + totalAddedLines, + totalAddedFiles, + } + } + public async sendDocGenerationTelemetryEvent(docGenerationEvent: DocGenerationEvent) { + const client = await this.proxyClient.getClient() + try { + const params: SendTelemetryEventRequest = { + telemetryEvent: { + docGenerationEvent, + }, + optOutPreference: getOptOutPreference(), + userContext: { + ideCategory: 'VSCODE', + operatingSystem: getOperatingSystem(), + product: 'DocGeneration', // Should be the same as in JetBrains + clientId: getClientId(globals.globalState), + ideVersion: extensionVersion, + }, + } + const response = await client.sendTelemetryEvent(params).promise() + getLogger().debug( + `${featureName}: successfully sent docGenerationEvent: ConversationId: ${docGenerationEvent.conversationId} RequestId: ${response.$response.requestId}` + ) + } catch (e) { + getLogger().error( + `${featureName}: failed to send doc generation telemetry: ${(e as Error).name}: ${ + (e as Error).message + } RequestId: ${(e as any).requestId}` + ) + } + } + + get currentCodeGenerationId() { + return this.state.currentCodeGenerationId + } + + get uploadId() { + if (!('uploadId' in this.state)) { + throw new Error("UploadId has to be initialized before it's read") + } + return this.state.uploadId + } + + get conversationId() { + if (!this._conversationId) { + throw new ConversationIdNotFoundError() + } + return this._conversationId + } + + // Used for cases where it is not needed to have conversationId + get conversationIdUnsafe() { + return this._conversationId + } + + get latestMessage() { + return this._latestMessage + } + + get telemetry() { + return this._telemetry + } +} diff --git a/packages/core/src/amazonqDoc/session/sessionState.ts b/packages/core/src/amazonqDoc/session/sessionState.ts new file mode 100644 index 00000000000..03e234a9429 --- /dev/null +++ b/packages/core/src/amazonqDoc/session/sessionState.ts @@ -0,0 +1,386 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { ToolkitError } from '../../shared/errors' +import globals from '../../shared/extensionGlobals' +import { getLogger } from '../../shared/logger' +import { telemetry } from '../../shared/telemetry/telemetry' +import { VirtualFileSystem } from '../../shared/virtualFilesystem' +import { DocGenerationStep, docScheme, getFileSummaryPercentage, Mode } from '../constants' + +import { CodeReference, UploadHistory } from '../../amazonq/webview/ui/connector' +import { AuthUtil } from '../../codewhisperer/util/authUtil' +import { randomUUID } from '../../shared/crypto' +import { i18n } from '../../shared/i18n-helper' + +import { + CodeGenerationStatus, + CurrentWsFolders, + DeletedFileInfo, + DevPhase, + NewFileInfo, + SessionState, + SessionStateAction, + SessionStateConfig, + SessionStateInteraction, + SessionStatePhase, +} from '../types' +import { + EmptyCodeGenID, + Intent, + TelemetryHelper, + getDeletedFileInfos, + prepareRepoData, + registerNewFiles, +} from '../../amazonqFeatureDev' +import { uploadCode } from '../../amazonqFeatureDev/util/upload' +import { + ContentLengthError, + DocServiceError, + NoChangeRequiredException, + PromptRefusalException, + PromptTooVagueError, + PromptUnrelatedError, + ReadmeTooLargeError, + WorkspaceEmptyError, +} from '../errors' +import { DocMessenger } from '../messenger' + +abstract class CodeGenBase { + private pollCount = 360 + private requestDelay = 5000 + public tokenSource: vscode.CancellationTokenSource + public phase: SessionStatePhase = DevPhase.CODEGEN + public readonly conversationId: string + public readonly uploadId: string + public currentCodeGenerationId?: string + public isCancellationRequested?: boolean + + constructor( + protected config: SessionStateConfig, + public tabID: string + ) { + this.tokenSource = new vscode.CancellationTokenSource() + this.conversationId = config.conversationId + this.uploadId = config.uploadId + this.currentCodeGenerationId = config.currentCodeGenerationId || EmptyCodeGenID + } + + async generateCode({ + messenger, + fs, + codeGenerationId, + telemetry: telemetry, + workspaceFolders, + mode, + }: { + messenger: DocMessenger + fs: VirtualFileSystem + codeGenerationId: string + telemetry: TelemetryHelper + workspaceFolders: CurrentWsFolders + mode: Mode + }): Promise<{ + newFiles: NewFileInfo[] + deletedFiles: DeletedFileInfo[] + references: CodeReference[] + codeGenerationRemainingIterationCount?: number + codeGenerationTotalIterationCount?: number + }> { + for ( + let pollingIteration = 0; + pollingIteration < this.pollCount && !this.isCancellationRequested; + ++pollingIteration + ) { + const codegenResult = await this.config.proxyClient.getCodeGeneration(this.conversationId, codeGenerationId) + const codeGenerationRemainingIterationCount = codegenResult.codeGenerationRemainingIterationCount + const codeGenerationTotalIterationCount = codegenResult.codeGenerationTotalIterationCount + + getLogger().debug(`Codegen response: %O`, codegenResult) + telemetry.setCodeGenerationResult(codegenResult.codeGenerationStatus.status) + switch (codegenResult.codeGenerationStatus.status as CodeGenerationStatus) { + case CodeGenerationStatus.COMPLETE: { + const { newFileContents, deletedFiles, references } = + await this.config.proxyClient.exportResultArchive(this.conversationId) + const newFileInfo = registerNewFiles( + fs, + newFileContents, + this.uploadId, + workspaceFolders, + this.conversationId, + docScheme + ) + telemetry.setNumberOfFilesGenerated(newFileInfo.length) + messenger.sendDocProgress(this.tabID, DocGenerationStep.GENERATING_ARTIFACTS + 1, 100, mode) + + return { + newFiles: newFileInfo, + deletedFiles: getDeletedFileInfos(deletedFiles, workspaceFolders), + references, + codeGenerationRemainingIterationCount: codeGenerationRemainingIterationCount, + codeGenerationTotalIterationCount: codeGenerationTotalIterationCount, + } + } + case CodeGenerationStatus.PREDICT_READY: + case CodeGenerationStatus.IN_PROGRESS: { + if (codegenResult.codeGenerationStatusDetail) { + const progress = getFileSummaryPercentage(codegenResult.codeGenerationStatusDetail) + messenger.sendDocProgress( + this.tabID, + progress === 100 + ? DocGenerationStep.GENERATING_ARTIFACTS + : DocGenerationStep.SUMMARIZING_FILES, + progress, + mode + ) + } + await new Promise((f) => globals.clock.setTimeout(f, this.requestDelay)) + break + } + case CodeGenerationStatus.PREDICT_FAILED: + case CodeGenerationStatus.DEBATE_FAILED: + case CodeGenerationStatus.FAILED: { + // eslint-disable-next-line unicorn/no-null + messenger.sendUpdatePromptProgress(this.tabID, null) + switch (true) { + case codegenResult.codeGenerationStatusDetail?.includes('README_TOO_LARGE'): { + throw new ReadmeTooLargeError() + } + case codegenResult.codeGenerationStatusDetail?.includes('WORKSPACE_TOO_LARGE'): { + throw new ContentLengthError() + } + case codegenResult.codeGenerationStatusDetail?.includes('WORKSPACE_EMPTY'): { + throw new WorkspaceEmptyError() + } + case codegenResult.codeGenerationStatusDetail?.includes('PROMPT_UNRELATED'): { + throw new PromptUnrelatedError() + } + case codegenResult.codeGenerationStatusDetail?.includes('PROMPT_TOO_VAGUE'): { + throw new PromptTooVagueError() + } + case codegenResult.codeGenerationStatusDetail?.includes('PROMPT_REFUSAL'): { + throw new PromptRefusalException() + } + case codegenResult.codeGenerationStatusDetail?.includes('Guardrails'): { + throw new DocServiceError( + i18n('AWS.amazonq.doc.error.docGen.default'), + 'GuardrailsException' + ) + } + case codegenResult.codeGenerationStatusDetail?.includes('EmptyPatch'): { + if (codegenResult.codeGenerationStatusDetail?.includes('NO_CHANGE_REQUIRED')) { + throw new NoChangeRequiredException() + } + throw new DocServiceError( + i18n('AWS.amazonq.doc.error.docGen.default'), + 'EmptyPatchException' + ) + } + case codegenResult.codeGenerationStatusDetail?.includes('Throttling'): { + throw new DocServiceError( + i18n('AWS.amazonq.featureDev.error.throttling'), + 'ThrottlingException' + ) + } + default: { + throw new ToolkitError(i18n('AWS.amazonq.doc.error.docGen.default'), { + code: 'DocGenerationFailed', + }) + } + } + } + default: { + const errorMessage = `Unknown status: ${codegenResult.codeGenerationStatus.status}\n` + throw new ToolkitError(errorMessage, { code: 'UnknownDocGenerationError' }) + } + } + } + if (!this.isCancellationRequested) { + // still in progress + const errorMessage = i18n('AWS.amazonq.featureDev.error.codeGen.timeout') + throw new ToolkitError(errorMessage, { code: 'DocGenerationTimeout' }) + } + return { + newFiles: [], + deletedFiles: [], + references: [], + } + } +} + +export class CodeGenState extends CodeGenBase implements SessionState { + constructor( + config: SessionStateConfig, + public filePaths: NewFileInfo[], + public deletedFiles: DeletedFileInfo[], + public references: CodeReference[], + tabID: string, + public currentIteration: number, + public uploadHistory: UploadHistory, + public codeGenerationRemainingIterationCount?: number, + public codeGenerationTotalIterationCount?: number + ) { + super(config, tabID) + } + + async interact(action: SessionStateAction): Promise { + return telemetry.amazonq_codeGenerationInvoke.run(async (span) => { + try { + action.tokenSource?.token.onCancellationRequested(() => { + this.isCancellationRequested = true + if (action.tokenSource) { + this.tokenSource = action.tokenSource + } + }) + + span.record({ + amazonqConversationId: this.config.conversationId, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + + action.telemetry.setGenerateCodeIteration(this.currentIteration) + action.telemetry.setGenerateCodeLastInvocationTime() + const codeGenerationId = randomUUID() + if (!action.tokenSource?.token.isCancellationRequested) { + action.messenger.sendDocProgress(this.tabID, DocGenerationStep.SUMMARIZING_FILES, 0, action.mode) + } + await this.config.proxyClient.startCodeGeneration( + this.config.conversationId, + this.config.uploadId, + action.msg, + Intent.DOC, + codeGenerationId, + undefined, + action.folderPath ? { documentation: { type: 'README', scope: action.folderPath } } : undefined + ) + + const codeGeneration = await this.generateCode({ + messenger: action.messenger, + fs: action.fs, + codeGenerationId, + telemetry: action.telemetry, + workspaceFolders: this.config.workspaceFolders, + mode: action.mode, + }) + + if (codeGeneration && !action.tokenSource?.token.isCancellationRequested) { + this.config.currentCodeGenerationId = codeGenerationId + this.currentCodeGenerationId = codeGenerationId + } + + this.filePaths = codeGeneration.newFiles + this.deletedFiles = codeGeneration.deletedFiles + this.references = codeGeneration.references + this.codeGenerationRemainingIterationCount = codeGeneration.codeGenerationRemainingIterationCount + this.codeGenerationTotalIterationCount = codeGeneration.codeGenerationTotalIterationCount + + if (action.uploadHistory && !action.uploadHistory[codeGenerationId] && codeGenerationId) { + action.uploadHistory[codeGenerationId] = { + timestamp: Date.now(), + uploadId: this.config.uploadId, + filePaths: codeGeneration.newFiles, + deletedFiles: codeGeneration.deletedFiles, + tabId: this.tabID, + } + } + + action.telemetry.setAmazonqNumberOfReferences(this.references.length) + action.telemetry.recordUserCodeGenerationTelemetry(span, this.conversationId) + const nextState = new PrepareCodeGenState( + this.config, + this.filePaths, + this.deletedFiles, + this.references, + this.tabID, + this.currentIteration + 1, + this.codeGenerationRemainingIterationCount, + this.codeGenerationTotalIterationCount, + action.uploadHistory, + this.tokenSource, + this.currentCodeGenerationId, + codeGenerationId + ) + return { + nextState, + interaction: {}, + } + } catch (e) { + throw e instanceof ToolkitError + ? e + : ToolkitError.chain(e, 'Server side error', { code: 'UnhandledCodeGenServerSideError' }) + } + }) + } +} + +export class PrepareCodeGenState implements SessionState { + public tokenSource: vscode.CancellationTokenSource + public readonly phase = DevPhase.CODEGEN + public uploadId: string + public conversationId: string + constructor( + private config: SessionStateConfig, + public filePaths: NewFileInfo[], + public deletedFiles: DeletedFileInfo[], + public references: CodeReference[], + public tabID: string, + public currentIteration: number, + public codeGenerationRemainingIterationCount?: number, + public codeGenerationTotalIterationCount?: number, + public uploadHistory: UploadHistory = {}, + public superTokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource(), + public currentCodeGenerationId?: string, + public codeGenerationId?: string + ) { + this.tokenSource = superTokenSource || new vscode.CancellationTokenSource() + this.uploadId = config.uploadId + this.currentCodeGenerationId = currentCodeGenerationId + this.conversationId = config.conversationId + this.uploadHistory = uploadHistory + this.codeGenerationId = codeGenerationId + } + + updateWorkspaceRoot(workspaceRoot: string) { + this.config.workspaceRoots = [workspaceRoot] + } + + async interact(action: SessionStateAction): Promise { + const uploadId = await telemetry.amazonq_createUpload.run(async (span) => { + span.record({ + amazonqConversationId: this.config.conversationId, + credentialStartUrl: AuthUtil.instance.startUrl, + }) + const { zipFileBuffer, zipFileChecksum } = await prepareRepoData( + this.config.workspaceRoots, + this.config.workspaceFolders, + action.telemetry, + span + ) + const uploadId = randomUUID() + const { uploadUrl, kmsKeyArn } = await this.config.proxyClient.createUploadUrl( + this.config.conversationId, + zipFileChecksum, + zipFileBuffer.length, + uploadId + ) + + await uploadCode(uploadUrl, zipFileBuffer, zipFileChecksum, kmsKeyArn) + + return uploadId + }) + this.uploadId = uploadId + const nextState = new CodeGenState( + { ...this.config, uploadId }, + this.filePaths, + this.deletedFiles, + this.references, + this.tabID, + this.currentIteration, + this.uploadHistory + ) + return nextState.interact(action) + } +} diff --git a/packages/core/src/amazonqDoc/storages/chatSession.ts b/packages/core/src/amazonqDoc/storages/chatSession.ts new file mode 100644 index 00000000000..34fb9f5404e --- /dev/null +++ b/packages/core/src/amazonqDoc/storages/chatSession.ts @@ -0,0 +1,23 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BaseChatSessionStorage } from '../../amazonq/commons/baseChatStorage' +import { createSessionConfig } from '../../amazonq/commons/session/sessionConfigFactory' +import { docScheme } from '../constants' +import { DocMessenger } from '../messenger' +import { Session } from '../session/session' + +export class DocChatSessionStorage extends BaseChatSessionStorage { + constructor(protected readonly messenger: DocMessenger) { + super() + } + + override async createSession(tabID: string): Promise { + const sessionConfig = await createSessionConfig(docScheme) + const session = new Session(sessionConfig, this.messenger, tabID) + this.sessions.set(tabID, session) + return session + } +} diff --git a/packages/core/src/amazonqDoc/types.ts b/packages/core/src/amazonqDoc/types.ts new file mode 100644 index 00000000000..3c9a510e2cb --- /dev/null +++ b/packages/core/src/amazonqDoc/types.ts @@ -0,0 +1,71 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItemButton, MynahIcons, ProgressField } from '@aws/mynah-ui' +import { + LLMResponseType, + SessionStorage, + SessionInfo, + DeletedFileInfo, + NewFileInfo, + NewFileZipContents, + SessionStateConfig, + SessionStatePhase, + DevPhase, + Interaction, + CurrentWsFolders, + CodeGenerationStatus, + SessionState as FeatureDevSessionState, + SessionStateAction as FeatureDevSessionStateAction, + SessionStateInteraction as FeatureDevSessionStateInteraction, +} from '../amazonqFeatureDev/types' + +import { Mode } from './constants' +import { DocMessenger } from './messenger' + +export const cancelDocGenButton: ChatItemButton = { + id: 'cancel-doc-generation', + text: 'Cancel', + icon: 'cancel' as MynahIcons, +} + +export const inProgress = (progress: number, text: string): ProgressField => { + return { + status: 'default', + text, + value: progress === 100 ? -1 : progress, + actions: [cancelDocGenButton], + } +} + +export interface SessionStateInteraction extends FeatureDevSessionStateInteraction { + nextState: SessionState | Omit | undefined + interaction: Interaction +} + +export interface SessionState extends FeatureDevSessionState { + interact(action: SessionStateAction): Promise +} + +export interface SessionStateAction extends FeatureDevSessionStateAction { + messenger: DocMessenger + mode: Mode + folderPath?: string +} + +export { + LLMResponseType, + SessionStorage, + SessionInfo, + DeletedFileInfo, + NewFileInfo, + NewFileZipContents, + SessionStateConfig, + SessionStatePhase, + DevPhase, + Interaction, + CodeGenerationStatus, + CurrentWsFolders, +} diff --git a/packages/core/src/amazonqDoc/views/actions/uiMessageListener.ts b/packages/core/src/amazonqDoc/views/actions/uiMessageListener.ts new file mode 100644 index 00000000000..c6960b15fcc --- /dev/null +++ b/packages/core/src/amazonqDoc/views/actions/uiMessageListener.ts @@ -0,0 +1,168 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatControllerEventEmitters } from '../../controllers/chat/controller' +import { MessageListener } from '../../../amazonq/messages/messageListener' +import { ExtensionMessage } from '../../../amazonq/webview/ui/commands' + +export interface UIMessageListenerProps { + readonly chatControllerEventEmitters: ChatControllerEventEmitters + readonly webViewMessageListener: MessageListener +} + +export class UIMessageListener { + private docGenerationControllerEventsEmitters: ChatControllerEventEmitters | undefined + private webViewMessageListener: MessageListener + + constructor(props: UIMessageListenerProps) { + this.docGenerationControllerEventsEmitters = props.chatControllerEventEmitters + this.webViewMessageListener = props.webViewMessageListener + + // Now we are listening to events that get sent from amazonq/webview/actions/actionListener (e.g. the tab) + this.webViewMessageListener.onMessage((msg) => { + this.handleMessage(msg) + }) + } + + private handleMessage(msg: ExtensionMessage) { + switch (msg.command) { + case 'chat-prompt': + this.processChatMessage(msg) + break + case 'follow-up-was-clicked': + this.followUpClicked(msg) + break + case 'open-diff': + this.openDiff(msg) + break + case 'chat-item-voted': + this.chatItemVoted(msg) + break + case 'chat-item-feedback': + this.chatItemFeedback(msg) + break + case 'stop-response': + this.stopResponse(msg) + break + case 'new-tab-was-created': + this.tabOpened(msg) + break + case 'tab-was-removed': + this.tabClosed(msg) + break + case 'auth-follow-up-was-clicked': + this.authClicked(msg) + break + case 'response-body-link-click': + this.processResponseBodyLinkClick(msg) + break + case 'insert_code_at_cursor_position': + this.insertCodeAtPosition(msg) + break + case 'file-click': + this.fileClicked(msg) + break + case 'form-action-click': + this.formActionClicked(msg) + break + } + } + + private chatItemVoted(msg: any) { + this.docGenerationControllerEventsEmitters?.processChatItemVotedMessage.fire({ + tabID: msg.tabID, + command: msg.command, + vote: msg.vote, + messageId: msg.messageId, + }) + } + + private chatItemFeedback(msg: any) { + this.docGenerationControllerEventsEmitters?.processChatItemFeedbackMessage.fire(msg) + } + + private processChatMessage(msg: any) { + this.docGenerationControllerEventsEmitters?.processHumanChatMessage.fire({ + message: msg.chatMessage, + tabID: msg.tabID, + }) + } + + private followUpClicked(msg: any) { + this.docGenerationControllerEventsEmitters?.followUpClicked.fire({ + followUp: msg.followUp, + tabID: msg.tabID, + }) + } + + private formActionClicked(msg: any) { + this.docGenerationControllerEventsEmitters?.formActionClicked.fire({ + ...msg, + }) + } + + private fileClicked(msg: any) { + this.docGenerationControllerEventsEmitters?.fileClicked.fire({ + tabID: msg.tabID, + filePath: msg.filePath, + actionName: msg.actionName, + messageId: msg.messageId, + }) + } + + private openDiff(msg: any) { + this.docGenerationControllerEventsEmitters?.openDiff.fire({ + tabID: msg.tabID, + filePath: msg.filePath, + deleted: msg.deleted, + messageId: msg.messageId, + }) + } + + private stopResponse(msg: any) { + this.docGenerationControllerEventsEmitters?.stopResponse.fire({ + tabID: msg.tabID, + }) + } + + private tabOpened(msg: any) { + this.docGenerationControllerEventsEmitters?.tabOpened.fire({ + tabID: msg.tabID, + }) + } + + private tabClosed(msg: any) { + this.docGenerationControllerEventsEmitters?.tabClosed.fire({ + tabID: msg.tabID, + }) + } + + private authClicked(msg: any) { + this.docGenerationControllerEventsEmitters?.authClicked.fire({ + tabID: msg.tabID, + authType: msg.authType, + }) + } + + private processResponseBodyLinkClick(msg: any) { + this.docGenerationControllerEventsEmitters?.processResponseBodyLinkClick.fire({ + command: msg.command, + messageId: msg.messageId, + tabID: msg.tabID, + link: msg.link, + }) + } + + private insertCodeAtPosition(msg: any) { + this.docGenerationControllerEventsEmitters?.insertCodeAtPositionClicked.fire({ + command: msg.command, + messageId: msg.messageId, + tabID: msg.tabID, + code: msg.code, + insertionTargetType: msg.insertionTargetType, + codeReference: msg.codeReference, + }) + } +} diff --git a/packages/core/src/amazonqFeatureDev/app.ts b/packages/core/src/amazonqFeatureDev/app.ts index 3e5a103011a..99164f417be 100644 --- a/packages/core/src/amazonqFeatureDev/app.ts +++ b/packages/core/src/amazonqFeatureDev/app.ts @@ -12,13 +12,13 @@ import { MessageListener } from '../amazonq/messages/messageListener' import { fromQueryToParameters } from '../shared/utilities/uriUtils' import { getLogger } from '../shared/logger' import { TabIdNotFoundError } from './errors' -import { featureDevScheme } from './constants' -import { Messenger } from './controllers/chat/messenger/messenger' -import { AppToWebViewMessageDispatcher } from './views/connector/connector' +import { featureDevChat, featureDevScheme } from './constants' import globals from '../shared/extensionGlobals' -import { ChatSessionStorage } from './storages/chatSession' +import { FeatureDevChatSessionStorage } from './storages/chatSession' import { AuthUtil } from '../codewhisperer/util/authUtil' import { debounce } from 'lodash' +import { Messenger } from '../amazonq/commons/connector/baseMessenger' +import { AppToWebViewMessageDispatcher } from '../amazonq/commons/connector/connectorMessages' export function init(appContext: AmazonQAppInitContext) { const featureDevChatControllerEventEmitters: ChatControllerEventEmitters = { @@ -34,10 +34,14 @@ export function init(appContext: AmazonQAppInitContext) { processResponseBodyLinkClick: new vscode.EventEmitter(), insertCodeAtPositionClicked: new vscode.EventEmitter(), fileClicked: new vscode.EventEmitter(), + storeCodeResultMessageId: new vscode.EventEmitter(), } - const messenger = new Messenger(new AppToWebViewMessageDispatcher(appContext.getAppsToWebViewMessagePublisher())) - const sessionStorage = new ChatSessionStorage(messenger) + const messenger = new Messenger( + new AppToWebViewMessageDispatcher(appContext.getAppsToWebViewMessagePublisher()), + featureDevChat + ) + const sessionStorage = new FeatureDevChatSessionStorage(messenger) new FeatureDevController( featureDevChatControllerEventEmitters, diff --git a/packages/core/src/amazonqFeatureDev/client/codewhispererruntime-2022-11-11.json b/packages/core/src/amazonqFeatureDev/client/codewhispererruntime-2022-11-11.json index c8690b720ee..6f628b220cb 100644 --- a/packages/core/src/amazonqFeatureDev/client/codewhispererruntime-2022-11-11.json +++ b/packages/core/src/amazonqFeatureDev/client/codewhispererruntime-2022-11-11.json @@ -21,13 +21,25 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "CreateUploadUrlRequest" }, - "output": { "shape": "CreateUploadUrlResponse" }, + "input": { + "shape": "CreateUploadUrlRequest" + }, + "output": { + "shape": "CreateUploadUrlResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ], "idempotent": true }, @@ -37,14 +49,28 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "CreateTaskAssistConversationRequest" }, - "output": { "shape": "CreateTaskAssistConversationResponse" }, + "input": { + "shape": "CreateTaskAssistConversationRequest" + }, + "output": { + "shape": "CreateTaskAssistConversationResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ServiceQuotaExceededException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ServiceQuotaExceededException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "CreateUploadUrl": { @@ -53,16 +79,34 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "CreateUploadUrlRequest" }, - "output": { "shape": "CreateUploadUrlResponse" }, + "input": { + "shape": "CreateUploadUrlRequest" + }, + "output": { + "shape": "CreateUploadUrlResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ConflictException" }, - { "shape": "ServiceQuotaExceededException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ConflictException" + }, + { + "shape": "ServiceQuotaExceededException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ], "idempotent": true }, @@ -72,14 +116,28 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "DeleteTaskAssistConversationRequest" }, - "output": { "shape": "DeleteTaskAssistConversationResponse" }, + "input": { + "shape": "DeleteTaskAssistConversationRequest" + }, + "output": { + "shape": "DeleteTaskAssistConversationResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "GenerateCompletions": { @@ -88,13 +146,25 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "GenerateCompletionsRequest" }, - "output": { "shape": "GenerateCompletionsResponse" }, + "input": { + "shape": "GenerateCompletionsRequest" + }, + "output": { + "shape": "GenerateCompletionsResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "GetCodeAnalysis": { @@ -103,14 +173,58 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "GetCodeAnalysisRequest" }, - "output": { "shape": "GetCodeAnalysisResponse" }, + "input": { + "shape": "GetCodeAnalysisRequest" + }, + "output": { + "shape": "GetCodeAnalysisResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } + ] + }, + "GetCodeFixJob": { + "name": "GetCodeFixJob", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { + "shape": "GetCodeFixJobRequest" + }, + "output": { + "shape": "GetCodeFixJobResponse" + }, + "errors": [ + { + "shape": "ThrottlingException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "GetTaskAssistCodeGeneration": { @@ -119,15 +233,58 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "GetTaskAssistCodeGenerationRequest" }, - "output": { "shape": "GetTaskAssistCodeGenerationResponse" }, + "input": { + "shape": "GetTaskAssistCodeGenerationRequest" + }, + "output": { + "shape": "GetTaskAssistCodeGenerationResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ConflictException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ConflictException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } + ] + }, + "GetTestGeneration": { + "name": "GetTestGeneration", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { + "shape": "GetTestGenerationRequest" + }, + "output": { + "shape": "GetTestGenerationResponse" + }, + "errors": [ + { + "shape": "ThrottlingException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "GetTransformation": { @@ -136,14 +293,28 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "GetTransformationRequest" }, - "output": { "shape": "GetTransformationResponse" }, + "input": { + "shape": "GetTransformationRequest" + }, + "output": { + "shape": "GetTransformationResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "GetTransformationPlan": { @@ -152,14 +323,28 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "GetTransformationPlanRequest" }, - "output": { "shape": "GetTransformationPlanResponse" }, + "input": { + "shape": "GetTransformationPlanRequest" + }, + "output": { + "shape": "GetTransformationPlanResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "ListAvailableCustomizations": { @@ -168,13 +353,25 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "ListAvailableCustomizationsRequest" }, - "output": { "shape": "ListAvailableCustomizationsResponse" }, + "input": { + "shape": "ListAvailableCustomizationsRequest" + }, + "output": { + "shape": "ListAvailableCustomizationsResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "ListCodeAnalysisFindings": { @@ -183,14 +380,28 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "ListCodeAnalysisFindingsRequest" }, - "output": { "shape": "ListCodeAnalysisFindingsResponse" }, + "input": { + "shape": "ListCodeAnalysisFindingsRequest" + }, + "output": { + "shape": "ListCodeAnalysisFindingsResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "ListFeatureEvaluations": { @@ -199,13 +410,25 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "ListFeatureEvaluationsRequest" }, - "output": { "shape": "ListFeatureEvaluationsResponse" }, + "input": { + "shape": "ListFeatureEvaluationsRequest" + }, + "output": { + "shape": "ListFeatureEvaluationsResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "ResumeTransformation": { @@ -214,14 +437,28 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "ResumeTransformationRequest" }, - "output": { "shape": "ResumeTransformationResponse" }, + "input": { + "shape": "ResumeTransformationRequest" + }, + "output": { + "shape": "ResumeTransformationResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "SendTelemetryEvent": { @@ -230,13 +467,25 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "SendTelemetryEventRequest" }, - "output": { "shape": "SendTelemetryEventResponse" }, + "input": { + "shape": "SendTelemetryEventRequest" + }, + "output": { + "shape": "SendTelemetryEventResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ], "idempotent": true }, @@ -246,50 +495,156 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "StartCodeAnalysisRequest" }, - "output": { "shape": "StartCodeAnalysisResponse" }, + "input": { + "shape": "StartCodeAnalysisRequest" + }, + "output": { + "shape": "StartCodeAnalysisResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ConflictException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ConflictException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ], "idempotent": true }, + "StartCodeFixJob": { + "name": "StartCodeFixJob", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { + "shape": "StartCodeFixJobRequest" + }, + "output": { + "shape": "StartCodeFixJobResponse" + }, + "errors": [ + { + "shape": "ThrottlingException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } + ] + }, "StartTaskAssistCodeGeneration": { "name": "StartTaskAssistCodeGeneration", "http": { "method": "POST", "requestUri": "/" }, - "input": { "shape": "StartTaskAssistCodeGenerationRequest" }, - "output": { "shape": "StartTaskAssistCodeGenerationResponse" }, + "input": { + "shape": "StartTaskAssistCodeGenerationRequest" + }, + "output": { + "shape": "StartTaskAssistCodeGenerationResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ConflictException" }, - { "shape": "ServiceQuotaExceededException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ConflictException" + }, + { + "shape": "ServiceQuotaExceededException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, + "StartTestGeneration": { + "name": "StartTestGeneration", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { + "shape": "StartTestGenerationRequest" + }, + "output": { + "shape": "StartTestGenerationResponse" + }, + "errors": [ + { + "shape": "ThrottlingException" + }, + { + "shape": "ConflictException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } + ], + "idempotent": true + }, "StartTransformation": { "name": "StartTransformation", "http": { "method": "POST", "requestUri": "/" }, - "input": { "shape": "StartTransformationRequest" }, - "output": { "shape": "StartTransformationResponse" }, + "input": { + "shape": "StartTransformationRequest" + }, + "output": { + "shape": "StartTransformationResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ConflictException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ConflictException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] }, "StopTransformation": { @@ -298,14 +653,28 @@ "method": "POST", "requestUri": "/" }, - "input": { "shape": "StopTransformationRequest" }, - "output": { "shape": "StopTransformationResponse" }, + "input": { + "shape": "StopTransformationRequest" + }, + "output": { + "shape": "StopTransformationResponse" + }, "errors": [ - { "shape": "ThrottlingException" }, - { "shape": "ResourceNotFoundException" }, - { "shape": "InternalServerException" }, - { "shape": "ValidationException" }, - { "shape": "AccessDeniedException" } + { + "shape": "ThrottlingException" + }, + { + "shape": "ResourceNotFoundException" + }, + { + "shape": "InternalServerException" + }, + { + "shape": "ValidationException" + }, + { + "shape": "AccessDeniedException" + } ] } }, @@ -314,8 +683,12 @@ "type": "structure", "required": ["message"], "members": { - "message": { "shape": "String" }, - "reason": { "shape": "AccessDeniedExceptionReason" } + "message": { + "shape": "String" + }, + "reason": { + "shape": "AccessDeniedExceptionReason" + } }, "exception": true }, @@ -327,10 +700,18 @@ "type": "structure", "required": ["namespace", "propertyName", "propertyContext"], "members": { - "namespace": { "shape": "AppStudioStateNamespaceString" }, - "propertyName": { "shape": "AppStudioStatePropertyNameString" }, - "propertyValue": { "shape": "AppStudioStatePropertyValueString" }, - "propertyContext": { "shape": "AppStudioStatePropertyContextString" } + "namespace": { + "shape": "AppStudioStateNamespaceString" + }, + "propertyName": { + "shape": "AppStudioStatePropertyNameString" + }, + "propertyValue": { + "shape": "AppStudioStatePropertyValueString" + }, + "propertyContext": { + "shape": "AppStudioStatePropertyContextString" + } } }, "AppStudioStateNamespaceString": { @@ -353,8 +734,8 @@ }, "AppStudioStatePropertyValueString": { "type": "string", - "max": 1024, - "min": 1, + "max": 10240, + "min": 0, "sensitive": true }, "ArtifactId": { @@ -365,8 +746,12 @@ }, "ArtifactMap": { "type": "map", - "key": { "shape": "ArtifactType" }, - "value": { "shape": "UploadId" }, + "key": { + "shape": "ArtifactType" + }, + "value": { + "shape": "UploadId" + }, "max": 64, "min": 1 }, @@ -378,11 +763,21 @@ "type": "structure", "required": ["content"], "members": { - "messageId": { "shape": "MessageId" }, - "content": { "shape": "AssistantResponseMessageContentString" }, - "supplementaryWebLinks": { "shape": "SupplementaryWebLinks" }, - "references": { "shape": "References" }, - "followupPrompt": { "shape": "FollowupPrompt" } + "messageId": { + "shape": "MessageId" + }, + "content": { + "shape": "AssistantResponseMessageContentString" + }, + "supplementaryWebLinks": { + "shape": "SupplementaryWebLinks" + }, + "references": { + "shape": "References" + }, + "followupPrompt": { + "shape": "FollowupPrompt" + } } }, "AssistantResponseMessageContentString": { @@ -405,25 +800,55 @@ "type": "structure", "required": ["conversationId", "messageId"], "members": { - "conversationId": { "shape": "ConversationId" }, - "messageId": { "shape": "MessageId" }, - "customizationArn": { "shape": "CustomizationArn" }, - "userIntent": { "shape": "UserIntent" }, - "hasCodeSnippet": { "shape": "Boolean" }, - "programmingLanguage": { "shape": "ProgrammingLanguage" }, - "activeEditorTotalCharacters": { "shape": "Integer" }, - "timeToFirstChunkMilliseconds": { "shape": "Double" }, - "timeBetweenChunks": { "shape": "timeBetweenChunks" }, - "fullResponselatency": { "shape": "Double" }, - "requestLength": { "shape": "Integer" }, - "responseLength": { "shape": "Integer" }, - "numberOfCodeBlocks": { "shape": "Integer" }, - "hasProjectLevelContext": { "shape": "Boolean" } + "conversationId": { + "shape": "ConversationId" + }, + "messageId": { + "shape": "MessageId" + }, + "customizationArn": { + "shape": "CustomizationArn" + }, + "userIntent": { + "shape": "UserIntent" + }, + "hasCodeSnippet": { + "shape": "Boolean" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "activeEditorTotalCharacters": { + "shape": "Integer" + }, + "timeToFirstChunkMilliseconds": { + "shape": "Double" + }, + "timeBetweenChunks": { + "shape": "timeBetweenChunks" + }, + "fullResponselatency": { + "shape": "Double" + }, + "requestLength": { + "shape": "Integer" + }, + "responseLength": { + "shape": "Integer" + }, + "numberOfCodeBlocks": { + "shape": "Integer" + }, + "hasProjectLevelContext": { + "shape": "Boolean" + } } }, "ChatHistory": { "type": "list", - "member": { "shape": "ChatMessage" }, + "member": { + "shape": "ChatMessage" + }, "max": 10, "min": 0 }, @@ -435,11 +860,14 @@ "messageId": { "shape": "MessageId" }, "customizationArn": { "shape": "CustomizationArn" }, "interactionType": { "shape": "ChatMessageInteractionType" }, - "interactionTarget": { "shape": "ChatInteractWithMessageEventInteractionTargetString" }, + "interactionTarget": { + "shape": "ChatInteractWithMessageEventInteractionTargetString" + }, "acceptedCharacterCount": { "shape": "Integer" }, "acceptedLineCount": { "shape": "Integer" }, "acceptedSnippetHasReference": { "shape": "Boolean" }, - "hasProjectLevelContext": { "shape": "Boolean" } + "hasProjectLevelContext": { "shape": "Boolean" }, + "userIntent": { "shape": "UserIntent" } } }, "ChatInteractWithMessageEventInteractionTargetString": { @@ -450,8 +878,12 @@ "ChatMessage": { "type": "structure", "members": { - "userInputMessage": { "shape": "UserInputMessage" }, - "assistantResponseMessage": { "shape": "AssistantResponseMessage" } + "userInputMessage": { + "shape": "UserInputMessage" + }, + "assistantResponseMessage": { + "shape": "AssistantResponseMessage" + } }, "union": true }, @@ -471,18 +903,30 @@ }, "ChatTriggerType": { "type": "string", - "enum": ["MANUAL", "DIAGNOSTIC"] + "enum": ["MANUAL", "DIAGNOSTIC", "INLINE_CHAT"] }, "ChatUserModificationEvent": { "type": "structure", "required": ["conversationId", "messageId", "modificationPercentage"], "members": { - "conversationId": { "shape": "ConversationId" }, - "customizationArn": { "shape": "CustomizationArn" }, - "messageId": { "shape": "MessageId" }, - "programmingLanguage": { "shape": "ProgrammingLanguage" }, - "modificationPercentage": { "shape": "Double" }, - "hasProjectLevelContext": { "shape": "Boolean" } + "conversationId": { + "shape": "ConversationId" + }, + "customizationArn": { + "shape": "CustomizationArn" + }, + "messageId": { + "shape": "MessageId" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "modificationPercentage": { + "shape": "Double" + }, + "hasProjectLevelContext": { + "shape": "Boolean" + } } }, "CodeAnalysisFindingsSchema": { @@ -501,7 +945,9 @@ "type": "structure", "required": ["codeScanName"], "members": { - "codeScanName": { "shape": "CodeScanName" } + "codeScanName": { + "shape": "CodeScanName" + } } }, "CodeCoverageEvent": { @@ -513,7 +959,82 @@ "acceptedCharacterCount": { "shape": "PrimitiveInteger" }, "totalCharacterCount": { "shape": "PrimitiveInteger" }, "timestamp": { "shape": "Timestamp" }, - "unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" } + "unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" }, + "totalNewCodeCharacterCount": { "shape": "PrimitiveInteger" }, + "totalNewCodeLineCount": { "shape": "PrimitiveInteger" } + } + }, + "CodeFixAcceptanceEvent": { + "type": "structure", + "required": ["jobId"], + "members": { + "jobId": { + "shape": "String" + }, + "ruleId": { + "shape": "String" + }, + "detectorId": { + "shape": "String" + }, + "findingId": { + "shape": "String" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "linesOfCodeAccepted": { + "shape": "Integer" + }, + "charsOfCodeAccepted": { + "shape": "Integer" + } + } + }, + "CodeFixGenerationEvent": { + "type": "structure", + "required": ["jobId"], + "members": { + "jobId": { + "shape": "String" + }, + "ruleId": { + "shape": "String" + }, + "detectorId": { + "shape": "String" + }, + "findingId": { + "shape": "String" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "linesOfCodeGenerated": { + "shape": "Integer" + }, + "charsOfCodeGenerated": { + "shape": "Integer" + } + } + }, + "CodeFixJobStatus": { + "type": "string", + "enum": ["Succeeded", "InProgress", "Failed"] + }, + "CodeFixName": { + "type": "string", + "max": 128, + "min": 1, + "pattern": "[a-zA-Z0-9-_$:.]*" + }, + "CodeFixUploadContext": { + "type": "structure", + "required": ["codeFixName"], + "members": { + "codeFixName": { + "shape": "CodeFixName" + } } }, "CodeGenerationId": { @@ -525,8 +1046,12 @@ "type": "structure", "required": ["status", "currentStage"], "members": { - "status": { "shape": "CodeGenerationWorkflowStatus" }, - "currentStage": { "shape": "CodeGenerationWorkflowStage" } + "status": { + "shape": "CodeGenerationWorkflowStatus" + }, + "currentStage": { + "shape": "CodeGenerationWorkflowStage" + } } }, "CodeGenerationStatusDetail": { @@ -542,6 +1067,24 @@ "enum": ["InProgress", "Complete", "Failed"] }, "CodeScanEvent": { + "type": "structure", + "required": ["programmingLanguage", "codeScanJobId", "timestamp"], + "members": { + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "codeScanJobId": { + "shape": "CodeScanJobId" + }, + "timestamp": { + "shape": "Timestamp" + }, + "codeAnalysisScope": { + "shape": "CodeAnalysisScope" + } + } + }, + "CodeScanFailedEvent": { "type": "structure", "required": ["programmingLanguage", "codeScanJobId", "timestamp"], "members": { @@ -565,7 +1108,9 @@ "type": "structure", "members": { "programmingLanguage": { "shape": "ProgrammingLanguage" }, - "CodeScanRemediationsEventType": { "shape": "CodeScanRemediationsEventType" }, + "CodeScanRemediationsEventType": { + "shape": "CodeScanRemediationsEventType" + }, "timestamp": { "shape": "Timestamp" }, "detectorId": { "shape": "String" }, "findingId": { "shape": "String" }, @@ -580,13 +1125,30 @@ "type": "string", "enum": ["CODESCAN_ISSUE_HOVER", "CODESCAN_ISSUE_APPLY_FIX", "CODESCAN_ISSUE_VIEW_DETAILS"] }, + "CodeScanSucceededEvent": { + "type": "structure", + "required": ["programmingLanguage", "codeScanJobId", "timestamp", "numberOfFindings"], + "members": { + "programmingLanguage": { "shape": "ProgrammingLanguage" }, + "codeScanJobId": { "shape": "CodeScanJobId" }, + "timestamp": { "shape": "Timestamp" }, + "numberOfFindings": { "shape": "PrimitiveInteger" }, + "codeAnalysisScope": { "shape": "CodeAnalysisScope" } + } + }, "Completion": { "type": "structure", "required": ["content"], "members": { - "content": { "shape": "CompletionContentString" }, - "references": { "shape": "References" }, - "mostRelevantMissingImports": { "shape": "Imports" } + "content": { + "shape": "CompletionContentString" + }, + "references": { + "shape": "References" + }, + "mostRelevantMissingImports": { + "shape": "Imports" + } } }, "CompletionContentString": { @@ -601,7 +1163,9 @@ }, "Completions": { "type": "list", - "member": { "shape": "Completion" }, + "member": { + "shape": "Completion" + }, "max": 10, "min": 0 }, @@ -609,10 +1173,42 @@ "type": "structure", "required": ["message"], "members": { - "message": { "shape": "String" } + "message": { + "shape": "String" + }, + "reason": { + "shape": "ConflictExceptionReason" + } }, "exception": true }, + "ConflictExceptionReason": { + "type": "string", + "enum": ["CUSTOMER_KMS_KEY_INVALID_KEY_POLICY", "CUSTOMER_KMS_KEY_DISABLED", "MISMATCHED_KMS_KEY"] + }, + "ConsoleState": { + "type": "structure", + "members": { + "region": { + "shape": "String" + }, + "consoleUrl": { + "shape": "SensitiveString" + }, + "serviceId": { + "shape": "String" + }, + "serviceConsolePage": { + "shape": "String" + }, + "serviceSubconsolePage": { + "shape": "String" + }, + "taskName": { + "shape": "SensitiveString" + } + } + }, "ContentChecksumType": { "type": "string", "enum": ["SHA_256"] @@ -630,11 +1226,21 @@ "type": "structure", "required": ["currentMessage", "chatTriggerType"], "members": { - "conversationId": { "shape": "ConversationId" }, - "history": { "shape": "ChatHistory" }, - "currentMessage": { "shape": "ChatMessage" }, - "chatTriggerType": { "shape": "ChatTriggerType" }, - "customizationArn": { "shape": "ResourceArn" } + "conversationId": { + "shape": "ConversationId" + }, + "history": { + "shape": "ChatHistory" + }, + "currentMessage": { + "shape": "ChatMessage" + }, + "chatTriggerType": { + "shape": "ChatTriggerType" + }, + "customizationArn": { + "shape": "ResourceArn" + } } }, "CreateTaskAssistConversationRequest": { @@ -645,19 +1251,24 @@ "type": "structure", "required": ["conversationId"], "members": { - "conversationId": { "shape": "ConversationId" } + "conversationId": { + "shape": "ConversationId" + } } }, "CreateUploadUrlRequest": { "type": "structure", "members": { "contentMd5": { "shape": "CreateUploadUrlRequestContentMd5String" }, - "contentChecksum": { "shape": "CreateUploadUrlRequestContentChecksumString" }, + "contentChecksum": { + "shape": "CreateUploadUrlRequestContentChecksumString" + }, "contentChecksumType": { "shape": "ContentChecksumType" }, "contentLength": { "shape": "CreateUploadUrlRequestContentLengthLong" }, "artifactType": { "shape": "ArtifactType" }, "uploadIntent": { "shape": "UploadIntent" }, - "uploadContext": { "shape": "UploadContext" } + "uploadContext": { "shape": "UploadContext" }, + "uploadId": { "shape": "UploadId" } } }, "CreateUploadUrlRequestContentChecksumString": { @@ -681,17 +1292,29 @@ "type": "structure", "required": ["uploadId", "uploadUrl"], "members": { - "uploadId": { "shape": "UploadId" }, - "uploadUrl": { "shape": "PreSignedUrl" }, - "kmsKeyArn": { "shape": "ResourceArn" }, - "requestHeaders": { "shape": "RequestHeaders" } + "uploadId": { + "shape": "UploadId" + }, + "uploadUrl": { + "shape": "PreSignedUrl" + }, + "kmsKeyArn": { + "shape": "ResourceArn" + }, + "requestHeaders": { + "shape": "RequestHeaders" + } } }, "CursorState": { "type": "structure", "members": { - "position": { "shape": "Position" }, - "range": { "shape": "Range" } + "position": { + "shape": "Position" + }, + "range": { + "shape": "Range" + } }, "union": true }, @@ -699,9 +1322,15 @@ "type": "structure", "required": ["arn"], "members": { - "arn": { "shape": "CustomizationArn" }, - "name": { "shape": "CustomizationName" }, - "description": { "shape": "Description" } + "arn": { + "shape": "CustomizationArn" + }, + "name": { + "shape": "CustomizationName" + }, + "description": { + "shape": "Description" + } } }, "CustomizationArn": { @@ -718,20 +1347,26 @@ }, "Customizations": { "type": "list", - "member": { "shape": "Customization" } + "member": { + "shape": "Customization" + } }, "DeleteTaskAssistConversationRequest": { "type": "structure", "required": ["conversationId"], "members": { - "conversationId": { "shape": "ConversationId" } + "conversationId": { + "shape": "ConversationId" + } } }, "DeleteTaskAssistConversationResponse": { "type": "structure", "required": ["conversationId"], "members": { - "conversationId": { "shape": "ConversationId" } + "conversationId": { + "shape": "ConversationId" + } } }, "Description": { @@ -743,8 +1378,12 @@ "Diagnostic": { "type": "structure", "members": { - "textDocumentDiagnostic": { "shape": "TextDocumentDiagnostic" }, - "runtimeDiagnostic": { "shape": "RuntimeDiagnostic" } + "textDocumentDiagnostic": { + "shape": "TextDocumentDiagnostic" + }, + "runtimeDiagnostic": { + "shape": "RuntimeDiagnostic" + } }, "union": true }, @@ -755,13 +1394,19 @@ "Dimension": { "type": "structure", "members": { - "name": { "shape": "DimensionNameString" }, - "value": { "shape": "DimensionValueString" } + "name": { + "shape": "DimensionNameString" + }, + "value": { + "shape": "DimensionValueString" + } } }, "DimensionList": { "type": "list", - "member": { "shape": "Dimension" }, + "member": { + "shape": "Dimension" + }, "max": 30, "min": 0 }, @@ -777,13 +1422,46 @@ "min": 1, "pattern": "[-a-zA-Z0-9._]*" }, + "DocGenerationEvent": { + "type": "structure", + "required": ["conversationId"], + "members": { + "conversationId": { "shape": "ConversationId" }, + "numberOfAddChars": { "shape": "PrimitiveInteger" }, + "numberOfAddLines": { "shape": "PrimitiveInteger" }, + "numberOfAddFiles": { "shape": "PrimitiveInteger" }, + "userDecision": { "shape": "DocGenerationUserDecision" }, + "interactionType": { "shape": "DocGenerationInteractionType" }, + "userIdentity": { "shape": "String" }, + "numberOfNavigation": { "shape": "PrimitiveInteger" }, + "folderLevel": { "shape": "DocGenerationFolderLevel" } + } + }, + "DocGenerationFolderLevel": { + "type": "string", + "enum": ["SUB_FOLDER", "ENTIRE_WORKSPACE"] + }, + "DocGenerationInteractionType": { + "type": "string", + "enum": ["GENERATE_README", "UPDATE_README", "EDIT_README"] + }, + "DocGenerationUserDecision": { + "type": "string", + "enum": ["ACCEPT", "REJECT"] + }, "DocumentSymbol": { "type": "structure", "required": ["name", "type"], "members": { - "name": { "shape": "DocumentSymbolNameString" }, - "type": { "shape": "SymbolType" }, - "source": { "shape": "DocumentSymbolSourceString" } + "name": { + "shape": "DocumentSymbolNameString" + }, + "type": { + "shape": "SymbolType" + }, + "source": { + "shape": "DocumentSymbolSourceString" + } } }, "DocumentSymbolNameString": { @@ -798,10 +1476,34 @@ }, "DocumentSymbols": { "type": "list", - "member": { "shape": "DocumentSymbol" }, + "member": { + "shape": "DocumentSymbol" + }, "max": 1000, "min": 0 }, + "DocumentationIntentContext": { + "type": "structure", + "required": ["type"], + "members": { + "scope": { + "shape": "DocumentationIntentContextScopeString" + }, + "type": { + "shape": "DocumentationType" + } + } + }, + "DocumentationIntentContextScopeString": { + "type": "string", + "max": 4096, + "min": 1, + "sensitive": true + }, + "DocumentationType": { + "type": "string", + "enum": ["README"] + }, "Double": { "type": "double", "box": true @@ -809,17 +1511,29 @@ "EditorState": { "type": "structure", "members": { - "document": { "shape": "TextDocument" }, - "cursorState": { "shape": "CursorState" }, - "relevantDocuments": { "shape": "RelevantDocumentList" } + "document": { + "shape": "TextDocument" + }, + "cursorState": { + "shape": "CursorState" + }, + "relevantDocuments": { + "shape": "RelevantDocumentList" + }, + "useRelevantDocuments": { + "shape": "Boolean" + } } }, "EnvState": { "type": "structure", "members": { "operatingSystem": { "shape": "EnvStateOperatingSystemString" }, - "currentWorkingDirectory": { "shape": "EnvStateCurrentWorkingDirectoryString" }, - "environmentVariables": { "shape": "EnvironmentVariables" } + "currentWorkingDirectory": { + "shape": "EnvStateCurrentWorkingDirectoryString" + }, + "environmentVariables": { "shape": "EnvironmentVariables" }, + "timezoneOffset": { "shape": "EnvStateTimezoneOffsetInteger" } } }, "EnvStateCurrentWorkingDirectoryString": { @@ -834,11 +1548,21 @@ "min": 1, "pattern": "(macos|linux|windows)" }, + "EnvStateTimezoneOffsetInteger": { + "type": "integer", + "box": true, + "max": 1440, + "min": -1440 + }, "EnvironmentVariable": { "type": "structure", "members": { - "key": { "shape": "EnvironmentVariableKeyString" }, - "value": { "shape": "EnvironmentVariableValueString" } + "key": { + "shape": "EnvironmentVariableKeyString" + }, + "value": { + "shape": "EnvironmentVariableValueString" + } } }, "EnvironmentVariableKeyString": { @@ -855,29 +1579,87 @@ }, "EnvironmentVariables": { "type": "list", - "member": { "shape": "EnvironmentVariable" }, + "member": { + "shape": "EnvironmentVariable" + }, "max": 100, "min": 0 }, + "FeatureDevCodeAcceptanceEvent": { + "type": "structure", + "required": ["conversationId", "linesOfCodeAccepted", "charactersOfCodeAccepted"], + "members": { + "conversationId": { "shape": "ConversationId" }, + "linesOfCodeAccepted": { + "shape": "FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger" + }, + "charactersOfCodeAccepted": { + "shape": "FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger" + }, + "programmingLanguage": { "shape": "ProgrammingLanguage" } + } + }, + "FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger": { + "type": "integer", + "min": 0 + }, + "FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger": { + "type": "integer", + "min": 0 + }, + "FeatureDevCodeGenerationEvent": { + "type": "structure", + "required": ["conversationId", "linesOfCodeGenerated", "charactersOfCodeGenerated"], + "members": { + "conversationId": { "shape": "ConversationId" }, + "linesOfCodeGenerated": { + "shape": "FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger" + }, + "charactersOfCodeGenerated": { + "shape": "FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + } + } + }, + "FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger": { + "type": "integer", + "min": 0 + }, + "FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger": { + "type": "integer", + "min": 0 + }, "FeatureDevEvent": { "type": "structure", "required": ["conversationId"], "members": { - "conversationId": { "shape": "ConversationId" } + "conversationId": { + "shape": "ConversationId" + } } }, "FeatureEvaluation": { "type": "structure", "required": ["feature", "variation", "value"], "members": { - "feature": { "shape": "FeatureName" }, - "variation": { "shape": "FeatureVariation" }, - "value": { "shape": "FeatureValue" } + "feature": { + "shape": "FeatureName" + }, + "variation": { + "shape": "FeatureVariation" + }, + "value": { + "shape": "FeatureValue" + } } }, "FeatureEvaluationsList": { "type": "list", - "member": { "shape": "FeatureEvaluation" }, + "member": { + "shape": "FeatureEvaluation" + }, "max": 50, "min": 0 }, @@ -890,10 +1672,18 @@ "FeatureValue": { "type": "structure", "members": { - "boolValue": { "shape": "Boolean" }, - "doubleValue": { "shape": "Double" }, - "longValue": { "shape": "Long" }, - "stringValue": { "shape": "FeatureValueStringType" } + "boolValue": { + "shape": "Boolean" + }, + "doubleValue": { + "shape": "Double" + }, + "longValue": { + "shape": "Long" + }, + "stringValue": { + "shape": "FeatureValueStringType" + } }, "union": true }, @@ -912,10 +1702,18 @@ "type": "structure", "required": ["leftFileContent", "rightFileContent", "filename", "programmingLanguage"], "members": { - "leftFileContent": { "shape": "FileContextLeftFileContentString" }, - "rightFileContent": { "shape": "FileContextRightFileContentString" }, - "filename": { "shape": "FileContextFilenameString" }, - "programmingLanguage": { "shape": "ProgrammingLanguage" } + "leftFileContent": { + "shape": "FileContextLeftFileContentString" + }, + "rightFileContent": { + "shape": "FileContextRightFileContentString" + }, + "filename": { + "shape": "FileContextFilenameString" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + } } }, "FileContextFilenameString": { @@ -940,8 +1738,12 @@ "type": "structure", "required": ["content"], "members": { - "content": { "shape": "FollowupPromptContentString" }, - "userIntent": { "shape": "UserIntent" } + "content": { + "shape": "FollowupPromptContentString" + }, + "userIntent": { + "shape": "UserIntent" + } } }, "FollowupPromptContentString": { @@ -955,9 +1757,13 @@ "required": ["fileContext"], "members": { "fileContext": { "shape": "FileContext" }, - "maxResults": { "shape": "GenerateCompletionsRequestMaxResultsInteger" }, + "maxResults": { + "shape": "GenerateCompletionsRequestMaxResultsInteger" + }, "nextToken": { "shape": "GenerateCompletionsRequestNextTokenString" }, - "referenceTrackerConfiguration": { "shape": "ReferenceTrackerConfiguration" }, + "referenceTrackerConfiguration": { + "shape": "ReferenceTrackerConfiguration" + }, "supplementalContexts": { "shape": "SupplementalContextList" }, "customizationArn": { "shape": "CustomizationArn" }, "optOutPreference": { "shape": "OptOutPreference" }, @@ -981,15 +1787,21 @@ "GenerateCompletionsResponse": { "type": "structure", "members": { - "completions": { "shape": "Completions" }, - "nextToken": { "shape": "SensitiveString" } + "completions": { + "shape": "Completions" + }, + "nextToken": { + "shape": "SensitiveString" + } } }, "GetCodeAnalysisRequest": { "type": "structure", "required": ["jobId"], "members": { - "jobId": { "shape": "GetCodeAnalysisRequestJobIdString" } + "jobId": { + "shape": "GetCodeAnalysisRequestJobIdString" + } } }, "GetCodeAnalysisRequestJobIdString": { @@ -1001,61 +1813,133 @@ "type": "structure", "required": ["status"], "members": { - "status": { "shape": "CodeAnalysisStatus" }, - "errorMessage": { "shape": "SensitiveString" } + "status": { + "shape": "CodeAnalysisStatus" + }, + "errorMessage": { + "shape": "SensitiveString" + } + } + }, + "GetCodeFixJobRequest": { + "type": "structure", + "required": ["jobId"], + "members": { + "jobId": { "shape": "GetCodeFixJobRequestJobIdString" } + } + }, + "GetCodeFixJobRequestJobIdString": { + "type": "string", + "max": 256, + "min": 1, + "pattern": ".*[A-Za-z0-9-:]+.*" + }, + "GetCodeFixJobResponse": { + "type": "structure", + "members": { + "jobStatus": { + "shape": "CodeFixJobStatus" + }, + "suggestedFix": { + "shape": "SuggestedFix" + } } }, "GetTaskAssistCodeGenerationRequest": { "type": "structure", "required": ["conversationId", "codeGenerationId"], "members": { - "conversationId": { "shape": "ConversationId" }, - "codeGenerationId": { "shape": "CodeGenerationId" } + "conversationId": { + "shape": "ConversationId" + }, + "codeGenerationId": { + "shape": "CodeGenerationId" + } } }, "GetTaskAssistCodeGenerationResponse": { "type": "structure", "required": ["conversationId", "codeGenerationStatus"], "members": { - "conversationId": { "shape": "ConversationId" }, - "codeGenerationStatus": { "shape": "CodeGenerationStatus" }, - "codeGenerationStatusDetail": { "shape": "CodeGenerationStatusDetail" }, - "codeGenerationRemainingIterationCount": { "shape": "Integer" }, - "codeGenerationTotalIterationCount": { "shape": "Integer" } + "conversationId": { + "shape": "ConversationId" + }, + "codeGenerationStatus": { + "shape": "CodeGenerationStatus" + }, + "codeGenerationStatusDetail": { + "shape": "CodeGenerationStatusDetail" + }, + "codeGenerationRemainingIterationCount": { + "shape": "Integer" + }, + "codeGenerationTotalIterationCount": { + "shape": "Integer" + } + } + }, + "GetTestGenerationRequest": { + "type": "structure", + "required": ["testGenerationJobGroupName", "testGenerationJobId"], + "members": { + "testGenerationJobGroupName": { + "shape": "TestGenerationJobGroupName" + }, + "testGenerationJobId": { + "shape": "UUID" + } + } + }, + "GetTestGenerationResponse": { + "type": "structure", + "members": { + "testGenerationJob": { + "shape": "TestGenerationJob" + } } }, "GetTransformationPlanRequest": { "type": "structure", "required": ["transformationJobId"], "members": { - "transformationJobId": { "shape": "TransformationJobId" } + "transformationJobId": { + "shape": "TransformationJobId" + } } }, "GetTransformationPlanResponse": { "type": "structure", "required": ["transformationPlan"], "members": { - "transformationPlan": { "shape": "TransformationPlan" } + "transformationPlan": { + "shape": "TransformationPlan" + } } }, "GetTransformationRequest": { "type": "structure", "required": ["transformationJobId"], "members": { - "transformationJobId": { "shape": "TransformationJobId" } + "transformationJobId": { + "shape": "TransformationJobId" + } } }, "GetTransformationResponse": { "type": "structure", "required": ["transformationJob"], "members": { - "transformationJob": { "shape": "TransformationJob" } + "transformationJob": { + "shape": "TransformationJob" + } } }, "GitState": { "type": "structure", "members": { - "status": { "shape": "GitStateStatusString" } + "status": { + "shape": "GitStateStatusString" + } } }, "GitStateStatusString": { @@ -1066,7 +1950,7 @@ }, "IdeCategory": { "type": "string", - "enum": ["JETBRAINS", "VSCODE", "CLI", "JUPYTER_MD", "JUPYTER_SM"], + "enum": ["JETBRAINS", "VSCODE", "CLI", "JUPYTER_MD", "JUPYTER_SM", "ECLIPSE", "VISUAL_STUDIO"], "max": 64, "min": 1 }, @@ -1078,7 +1962,9 @@ "Import": { "type": "structure", "members": { - "statement": { "shape": "ImportStatementString" } + "statement": { + "shape": "ImportStatementString" + } } }, "ImportStatementString": { @@ -1089,28 +1975,104 @@ }, "Imports": { "type": "list", - "member": { "shape": "Import" }, + "member": { + "shape": "Import" + }, "max": 10, "min": 0 }, + "InlineChatEvent": { + "type": "structure", + "required": ["requestId", "timestamp"], + "members": { + "requestId": { + "shape": "UUID" + }, + "timestamp": { + "shape": "Timestamp" + }, + "inputLength": { + "shape": "PrimitiveInteger" + }, + "numSelectedLines": { + "shape": "PrimitiveInteger" + }, + "numSuggestionAddChars": { + "shape": "PrimitiveInteger" + }, + "numSuggestionAddLines": { + "shape": "PrimitiveInteger" + }, + "numSuggestionDelChars": { + "shape": "PrimitiveInteger" + }, + "numSuggestionDelLines": { + "shape": "PrimitiveInteger" + }, + "codeIntent": { + "shape": "Boolean" + }, + "userDecision": { + "shape": "InlineChatUserDecision" + }, + "responseStartLatency": { + "shape": "Double" + }, + "responseEndLatency": { + "shape": "Double" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + } + } + }, + "InlineChatUserDecision": { + "type": "string", + "enum": ["ACCEPT", "REJECT", "DISMISS"] + }, "Integer": { "type": "integer", "box": true }, + "Intent": { + "type": "string", + "enum": ["DEV", "DOC"] + }, + "IntentContext": { + "type": "structure", + "members": { + "documentation": { + "shape": "DocumentationIntentContext" + } + }, + "union": true + }, "InternalServerException": { "type": "structure", "required": ["message"], "members": { - "message": { "shape": "String" } + "message": { + "shape": "String" + } }, "exception": true, "fault": true, - "retryable": { "throttling": false } + "retryable": { + "throttling": false + } + }, + "LineRangeList": { + "type": "list", + "member": { + "shape": "Range" + } }, "ListAvailableCustomizationsRequest": { "type": "structure", "members": { - "maxResults": { "shape": "ListAvailableCustomizationsRequestMaxResultsInteger" }, + "maxResults": { + "shape": "ListAvailableCustomizationsRequestMaxResultsInteger" + }, "nextToken": { "shape": "Base64EncodedPaginationToken" } } }, @@ -1124,17 +2086,27 @@ "type": "structure", "required": ["customizations"], "members": { - "customizations": { "shape": "Customizations" }, - "nextToken": { "shape": "Base64EncodedPaginationToken" } + "customizations": { + "shape": "Customizations" + }, + "nextToken": { + "shape": "Base64EncodedPaginationToken" + } } }, "ListCodeAnalysisFindingsRequest": { "type": "structure", "required": ["jobId", "codeAnalysisFindingsSchema"], "members": { - "jobId": { "shape": "ListCodeAnalysisFindingsRequestJobIdString" }, - "nextToken": { "shape": "PaginationToken" }, - "codeAnalysisFindingsSchema": { "shape": "CodeAnalysisFindingsSchema" } + "jobId": { + "shape": "ListCodeAnalysisFindingsRequestJobIdString" + }, + "nextToken": { + "shape": "PaginationToken" + }, + "codeAnalysisFindingsSchema": { + "shape": "CodeAnalysisFindingsSchema" + } } }, "ListCodeAnalysisFindingsRequestJobIdString": { @@ -1146,22 +2118,30 @@ "type": "structure", "required": ["codeAnalysisFindings"], "members": { - "nextToken": { "shape": "PaginationToken" }, - "codeAnalysisFindings": { "shape": "SensitiveString" } + "nextToken": { + "shape": "PaginationToken" + }, + "codeAnalysisFindings": { + "shape": "SensitiveString" + } } }, "ListFeatureEvaluationsRequest": { "type": "structure", "required": ["userContext"], "members": { - "userContext": { "shape": "UserContext" } + "userContext": { + "shape": "UserContext" + } } }, "ListFeatureEvaluationsResponse": { "type": "structure", "required": ["featureEvaluations"], "members": { - "featureEvaluations": { "shape": "FeatureEvaluationsList" } + "featureEvaluations": { + "shape": "FeatureEvaluationsList" + } } }, "Long": { @@ -1177,11 +2157,21 @@ "type": "structure", "required": ["metricName", "metricValue", "timestamp", "product"], "members": { - "metricName": { "shape": "MetricDataMetricNameString" }, - "metricValue": { "shape": "Double" }, - "timestamp": { "shape": "Timestamp" }, - "product": { "shape": "MetricDataProductString" }, - "dimensions": { "shape": "DimensionList" } + "metricName": { + "shape": "MetricDataMetricNameString" + }, + "metricValue": { + "shape": "Double" + }, + "timestamp": { + "shape": "Timestamp" + }, + "product": { + "shape": "MetricDataProductString" + }, + "dimensions": { + "shape": "DimensionList" + } } }, "MetricDataMetricNameString": { @@ -1216,8 +2206,12 @@ "type": "structure", "required": ["line", "character"], "members": { - "line": { "shape": "Integer" }, - "character": { "shape": "Integer" } + "line": { + "shape": "Integer" + }, + "character": { + "shape": "Integer" + } } }, "PreSignedUrl": { @@ -1226,7 +2220,9 @@ "min": 1, "sensitive": true }, - "PrimitiveInteger": { "type": "integer" }, + "PrimitiveInteger": { + "type": "integer" + }, "ProfileArn": { "type": "string", "max": 950, @@ -1237,25 +2233,33 @@ "type": "structure", "required": ["languageName"], "members": { - "languageName": { "shape": "ProgrammingLanguageLanguageNameString" } + "languageName": { + "shape": "ProgrammingLanguageLanguageNameString" + } } }, "ProgrammingLanguageLanguageNameString": { "type": "string", "max": 128, "min": 1, - "pattern": "(python|javascript|java|csharp|typescript|c|cpp|go|kotlin|php|ruby|rust|scala|shell|sql|json|yaml|vue|tf|tsx|jsx|plaintext)" + "pattern": "(python|javascript|java|csharp|typescript|c|cpp|go|kotlin|php|ruby|rust|scala|shell|sql|json|yaml|vue|tf|tsx|jsx|plaintext|systemverilog|dart|lua|swift|powershell|r)" }, "ProgressUpdates": { "type": "list", - "member": { "shape": "TransformationProgressUpdate" } + "member": { + "shape": "TransformationProgressUpdate" + } }, "Range": { "type": "structure", "required": ["start", "end"], "members": { - "start": { "shape": "Position" }, - "end": { "shape": "Position" } + "start": { + "shape": "Position" + }, + "end": { + "shape": "Position" + } } }, "RecommendationsWithReferencesPreference": { @@ -1265,10 +2269,18 @@ "Reference": { "type": "structure", "members": { - "licenseName": { "shape": "ReferenceLicenseNameString" }, - "repository": { "shape": "ReferenceRepositoryString" }, - "url": { "shape": "ReferenceUrlString" }, - "recommendationContentSpan": { "shape": "Span" } + "licenseName": { + "shape": "ReferenceLicenseNameString" + }, + "repository": { + "shape": "ReferenceRepositoryString" + }, + "url": { + "shape": "ReferenceUrlString" + }, + "recommendationContentSpan": { + "shape": "Span" + } } }, "ReferenceLicenseNameString": { @@ -1285,7 +2297,9 @@ "type": "structure", "required": ["recommendationsWithReferences"], "members": { - "recommendationsWithReferences": { "shape": "RecommendationsWithReferencesPreference" } + "recommendationsWithReferences": { + "shape": "RecommendationsWithReferencesPreference" + } } }, "ReferenceUrlString": { @@ -1295,13 +2309,17 @@ }, "References": { "type": "list", - "member": { "shape": "Reference" }, + "member": { + "shape": "Reference" + }, "max": 10, "min": 0 }, "RelevantDocumentList": { "type": "list", - "member": { "shape": "RelevantTextDocument" }, + "member": { + "shape": "RelevantTextDocument" + }, "max": 5, "min": 0 }, @@ -1309,7 +2327,9 @@ "type": "structure", "required": ["relativeFilePath"], "members": { - "relativeFilePath": { "shape": "RelevantTextDocumentRelativeFilePathString" }, + "relativeFilePath": { + "shape": "RelevantTextDocumentRelativeFilePathString" + }, "programmingLanguage": { "shape": "ProgrammingLanguage" }, "text": { "shape": "RelevantTextDocumentTextString" }, "documentSymbols": { "shape": "DocumentSymbols" } @@ -1339,8 +2359,12 @@ }, "RequestHeaders": { "type": "map", - "key": { "shape": "RequestHeaderKey" }, - "value": { "shape": "RequestHeaderValue" }, + "key": { + "shape": "RequestHeaderKey" + }, + "value": { + "shape": "RequestHeaderValue" + }, "max": 16, "min": 1, "sensitive": true @@ -1355,7 +2379,9 @@ "type": "structure", "required": ["message"], "members": { - "message": { "shape": "String" } + "message": { + "shape": "String" + } }, "exception": true }, @@ -1363,24 +2389,36 @@ "type": "structure", "required": ["transformationJobId"], "members": { - "transformationJobId": { "shape": "TransformationJobId" }, - "userActionStatus": { "shape": "TransformationUserActionStatus" } + "transformationJobId": { + "shape": "TransformationJobId" + }, + "userActionStatus": { + "shape": "TransformationUserActionStatus" + } } }, "ResumeTransformationResponse": { "type": "structure", "required": ["transformationStatus"], "members": { - "transformationStatus": { "shape": "TransformationStatus" } + "transformationStatus": { + "shape": "TransformationStatus" + } } }, "RuntimeDiagnostic": { "type": "structure", "required": ["source", "severity", "message"], "members": { - "source": { "shape": "RuntimeDiagnosticSourceString" }, - "severity": { "shape": "DiagnosticSeverity" }, - "message": { "shape": "RuntimeDiagnosticMessageString" } + "source": { + "shape": "RuntimeDiagnosticSourceString" + }, + "severity": { + "shape": "DiagnosticSeverity" + }, + "message": { + "shape": "RuntimeDiagnosticMessageString" + } } }, "RuntimeDiagnosticMessageString": { @@ -1403,10 +2441,18 @@ "shape": "IdempotencyToken", "idempotencyToken": true }, - "telemetryEvent": { "shape": "TelemetryEvent" }, - "optOutPreference": { "shape": "OptOutPreference" }, - "userContext": { "shape": "UserContext" }, - "profileArn": { "shape": "ProfileArn" } + "telemetryEvent": { + "shape": "TelemetryEvent" + }, + "optOutPreference": { + "shape": "OptOutPreference" + }, + "userContext": { + "shape": "UserContext" + }, + "profileArn": { + "shape": "ProfileArn" + } } }, "SendTelemetryEventResponse": { @@ -1421,13 +2467,17 @@ "type": "structure", "required": ["message"], "members": { - "message": { "shape": "String" } + "message": { + "shape": "String" + } }, "exception": true }, "ShellHistory": { "type": "list", - "member": { "shape": "ShellHistoryEntry" }, + "member": { + "shape": "ShellHistoryEntry" + }, "max": 20, "min": 0 }, @@ -1435,11 +2485,21 @@ "type": "structure", "required": ["command"], "members": { - "command": { "shape": "ShellHistoryEntryCommandString" }, - "directory": { "shape": "ShellHistoryEntryDirectoryString" }, - "exitCode": { "shape": "Integer" }, - "stdout": { "shape": "ShellHistoryEntryStdoutString" }, - "stderr": { "shape": "ShellHistoryEntryStderrString" } + "command": { + "shape": "ShellHistoryEntryCommandString" + }, + "directory": { + "shape": "ShellHistoryEntryDirectoryString" + }, + "exitCode": { + "shape": "Integer" + }, + "stdout": { + "shape": "ShellHistoryEntryStdoutString" + }, + "stderr": { + "shape": "ShellHistoryEntryStderrString" + } } }, "ShellHistoryEntryCommandString": { @@ -1470,8 +2530,12 @@ "type": "structure", "required": ["shellName"], "members": { - "shellName": { "shape": "ShellStateShellNameString" }, - "shellHistory": { "shape": "ShellHistory" } + "shellName": { + "shape": "ShellStateShellNameString" + }, + "shellHistory": { + "shape": "ShellHistory" + } } }, "ShellStateShellNameString": { @@ -1483,8 +2547,12 @@ "Span": { "type": "structure", "members": { - "start": { "shape": "SpanStartInteger" }, - "end": { "shape": "SpanEndInteger" } + "start": { + "shape": "SpanStartInteger" + }, + "end": { + "shape": "SpanEndInteger" + } } }, "SpanEndInteger": { @@ -1501,64 +2569,198 @@ "type": "structure", "required": ["artifacts", "programmingLanguage"], "members": { - "artifacts": { "shape": "ArtifactMap" }, - "programmingLanguage": { "shape": "ProgrammingLanguage" }, + "artifacts": { + "shape": "ArtifactMap" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, "clientToken": { "shape": "StartCodeAnalysisRequestClientTokenString", "idempotencyToken": true }, - "scope": { "shape": "CodeAnalysisScope" }, - "codeScanName": { "shape": "CodeScanName" } + "scope": { + "shape": "CodeAnalysisScope" + }, + "codeScanName": { + "shape": "CodeScanName" + } + } + }, + "StartCodeAnalysisRequestClientTokenString": { + "type": "string", + "max": 256, + "min": 1 + }, + "StartCodeAnalysisResponse": { + "type": "structure", + "required": ["jobId", "status"], + "members": { + "jobId": { + "shape": "StartCodeAnalysisResponseJobIdString" + }, + "status": { + "shape": "CodeAnalysisStatus" + }, + "errorMessage": { + "shape": "SensitiveString" + } + } + }, + "StartCodeAnalysisResponseJobIdString": { + "type": "string", + "max": 256, + "min": 1 + }, + "StartCodeFixJobRequest": { + "type": "structure", + "required": ["snippetRange", "uploadId"], + "members": { + "snippetRange": { + "shape": "Range" + }, + "uploadId": { + "shape": "UploadId" + }, + "description": { + "shape": "StartCodeFixJobRequestDescriptionString" + }, + "ruleId": { + "shape": "StartCodeFixJobRequestRuleIdString" + }, + "codeFixName": { + "shape": "CodeFixName" + } + } + }, + "StartCodeFixJobRequestDescriptionString": { + "type": "string", + "max": 2000, + "min": 1, + "sensitive": true + }, + "StartCodeFixJobRequestRuleIdString": { + "type": "string", + "max": 256, + "min": 1, + "pattern": ".*[A-Za-z0-9-]+.*" + }, + "StartCodeFixJobResponse": { + "type": "structure", + "members": { + "jobId": { + "shape": "StartCodeFixJobResponseJobIdString" + }, + "status": { + "shape": "CodeFixJobStatus" + } + } + }, + "StartCodeFixJobResponseJobIdString": { + "type": "string", + "max": 256, + "min": 1, + "pattern": ".*[A-Za-z0-9-:]+.*" + }, + "StartTaskAssistCodeGenerationRequest": { + "type": "structure", + "required": ["conversationState", "workspaceState"], + "members": { + "conversationState": { + "shape": "ConversationState" + }, + "workspaceState": { + "shape": "WorkspaceState" + }, + "taskAssistPlan": { + "shape": "TaskAssistPlan" + }, + "codeGenerationId": { + "shape": "CodeGenerationId" + }, + "currentCodeGenerationId": { + "shape": "CodeGenerationId" + }, + "intent": { + "shape": "Intent" + }, + "intentContext": { + "shape": "IntentContext" + } } }, - "StartCodeAnalysisRequestClientTokenString": { - "type": "string", - "max": 256, - "min": 1 + "StartTaskAssistCodeGenerationResponse": { + "type": "structure", + "required": ["conversationId", "codeGenerationId"], + "members": { + "conversationId": { + "shape": "ConversationId" + }, + "codeGenerationId": { + "shape": "CodeGenerationId" + } + } }, - "StartCodeAnalysisResponse": { + "StartTestGenerationRequest": { "type": "structure", - "required": ["jobId", "status"], + "required": ["uploadId", "targetCodeList", "userInput"], "members": { - "jobId": { "shape": "StartCodeAnalysisResponseJobIdString" }, - "status": { "shape": "CodeAnalysisStatus" }, - "errorMessage": { "shape": "SensitiveString" } + "uploadId": { + "shape": "UploadId" + }, + "targetCodeList": { + "shape": "TargetCodeList" + }, + "userInput": { + "shape": "StartTestGenerationRequestUserInputString" + }, + "testGenerationJobGroupName": { + "shape": "TestGenerationJobGroupName" + }, + "clientToken": { + "shape": "StartTestGenerationRequestClientTokenString", + "idempotencyToken": true + } } }, - "StartCodeAnalysisResponseJobIdString": { + "StartTestGenerationRequestClientTokenString": { "type": "string", "max": 256, "min": 1 }, - "StartTaskAssistCodeGenerationRequest": { - "type": "structure", - "required": ["conversationState", "workspaceState"], - "members": { - "conversationState": { "shape": "ConversationState" }, - "workspaceState": { "shape": "WorkspaceState" } - } + "StartTestGenerationRequestUserInputString": { + "type": "string", + "max": 4096, + "min": 0, + "sensitive": true }, - "StartTaskAssistCodeGenerationResponse": { + "StartTestGenerationResponse": { "type": "structure", - "required": ["conversationId", "codeGenerationId"], "members": { - "conversationId": { "shape": "ConversationId" }, - "codeGenerationId": { "shape": "CodeGenerationId" } + "testGenerationJob": { + "shape": "TestGenerationJob" + } } }, "StartTransformationRequest": { "type": "structure", "required": ["workspaceState", "transformationSpec"], "members": { - "workspaceState": { "shape": "WorkspaceState" }, - "transformationSpec": { "shape": "TransformationSpec" } + "workspaceState": { + "shape": "WorkspaceState" + }, + "transformationSpec": { + "shape": "TransformationSpec" + } } }, "StartTransformationResponse": { "type": "structure", "required": ["transformationJobId"], "members": { - "transformationJobId": { "shape": "TransformationJobId" } + "transformationJobId": { + "shape": "TransformationJobId" + } } }, "StepId": { @@ -1570,27 +2772,63 @@ "type": "structure", "required": ["transformationJobId"], "members": { - "transformationJobId": { "shape": "TransformationJobId" } + "transformationJobId": { + "shape": "TransformationJobId" + } } }, "StopTransformationResponse": { "type": "structure", "required": ["transformationStatus"], "members": { - "transformationStatus": { "shape": "TransformationStatus" } + "transformationStatus": { + "shape": "TransformationStatus" + } } }, - "String": { "type": "string" }, + "String": { + "type": "string" + }, + "SuggestedFix": { + "type": "structure", + "members": { + "codeDiff": { + "shape": "SuggestedFixCodeDiffString" + }, + "description": { + "shape": "SuggestedFixDescriptionString" + }, + "references": { + "shape": "References" + } + } + }, + "SuggestedFixCodeDiffString": { + "type": "string", + "max": 200000, + "min": 0, + "sensitive": true + }, + "SuggestedFixDescriptionString": { + "type": "string", + "max": 2000, + "min": 1, + "sensitive": true + }, "SuggestionState": { "type": "string", - "enum": ["ACCEPT", "REJECT", "DISCARD", "EMPTY"] + "enum": ["ACCEPT", "REJECT", "DISCARD", "EMPTY", "MERGE"] }, "SupplementalContext": { "type": "structure", "required": ["filePath", "content"], "members": { - "filePath": { "shape": "SupplementalContextFilePathString" }, - "content": { "shape": "SupplementalContextContentString" } + "filePath": { + "shape": "SupplementalContextFilePathString" + }, + "content": { + "shape": "SupplementalContextContentString" + } } }, "SupplementalContextContentString": { @@ -1607,7 +2845,9 @@ }, "SupplementalContextList": { "type": "list", - "member": { "shape": "SupplementalContext" }, + "member": { + "shape": "SupplementalContext" + }, "max": 5, "min": 0 }, @@ -1615,9 +2855,15 @@ "type": "structure", "required": ["url", "title"], "members": { - "url": { "shape": "SupplementaryWebLinkUrlString" }, - "title": { "shape": "SupplementaryWebLinkTitleString" }, - "snippet": { "shape": "SupplementaryWebLinkSnippetString" } + "url": { + "shape": "SupplementaryWebLinkUrlString" + }, + "title": { + "shape": "SupplementaryWebLinkTitleString" + }, + "snippet": { + "shape": "SupplementaryWebLinkSnippetString" + } } }, "SupplementaryWebLinkSnippetString": { @@ -1640,7 +2886,9 @@ }, "SupplementaryWebLinks": { "type": "list", - "member": { "shape": "SupplementaryWebLink" }, + "member": { + "shape": "SupplementaryWebLink" + }, "max": 10, "min": 0 }, @@ -1648,11 +2896,90 @@ "type": "string", "enum": ["DECLARATION", "USAGE"] }, + "TargetCode": { + "type": "structure", + "required": ["relativeTargetPath"], + "members": { + "relativeTargetPath": { + "shape": "TargetCodeRelativeTargetPathString" + }, + "targetLineRangeList": { + "shape": "LineRangeList" + } + } + }, + "TargetCodeList": { + "type": "list", + "member": { + "shape": "TargetCode" + }, + "min": 1 + }, + "TargetCodeRelativeTargetPathString": { + "type": "string", + "max": 4096, + "min": 1, + "sensitive": true + }, + "TaskAssistPlan": { + "type": "list", + "member": { + "shape": "TaskAssistPlanStep" + }, + "min": 0 + }, + "TaskAssistPlanStep": { + "type": "structure", + "required": ["filePath", "description"], + "members": { + "filePath": { + "shape": "TaskAssistPlanStepFilePathString" + }, + "description": { + "shape": "TaskAssistPlanStepDescriptionString" + }, + "startLine": { + "shape": "TaskAssistPlanStepStartLineInteger" + }, + "endLine": { + "shape": "TaskAssistPlanStepEndLineInteger" + }, + "action": { + "shape": "TaskAssistPlanStepAction" + } + } + }, + "TaskAssistPlanStepAction": { + "type": "string", + "enum": ["MODIFY", "CREATE", "DELETE", "UNKNOWN"] + }, + "TaskAssistPlanStepDescriptionString": { + "type": "string", + "max": 1024, + "min": 1 + }, + "TaskAssistPlanStepEndLineInteger": { + "type": "integer", + "box": true, + "min": 0 + }, + "TaskAssistPlanStepFilePathString": { + "type": "string", + "max": 1024, + "min": 1 + }, + "TaskAssistPlanStepStartLineInteger": { + "type": "integer", + "box": true, + "min": 0 + }, "TaskAssistPlanningUploadContext": { "type": "structure", "required": ["conversationId"], "members": { - "conversationId": { "shape": "ConversationId" } + "conversationId": { + "shape": "ConversationId" + } } }, "TelemetryEvent": { @@ -1662,20 +2989,40 @@ "codeCoverageEvent": { "shape": "CodeCoverageEvent" }, "userModificationEvent": { "shape": "UserModificationEvent" }, "codeScanEvent": { "shape": "CodeScanEvent" }, + "codeScanSucceededEvent": { "shape": "CodeScanSucceededEvent" }, + "codeScanFailedEvent": { "shape": "CodeScanFailedEvent" }, "codeScanRemediationsEvent": { "shape": "CodeScanRemediationsEvent" }, + "codeFixGenerationEvent": { "shape": "CodeFixGenerationEvent" }, + "codeFixAcceptanceEvent": { "shape": "CodeFixAcceptanceEvent" }, "metricData": { "shape": "MetricData" }, "chatAddMessageEvent": { "shape": "ChatAddMessageEvent" }, - "chatInteractWithMessageEvent": { "shape": "ChatInteractWithMessageEvent" }, + "chatInteractWithMessageEvent": { + "shape": "ChatInteractWithMessageEvent" + }, "chatUserModificationEvent": { "shape": "ChatUserModificationEvent" }, - "terminalUserInteractionEvent": { "shape": "TerminalUserInteractionEvent" }, - "featureDevEvent": { "shape": "FeatureDevEvent" } + "terminalUserInteractionEvent": { + "shape": "TerminalUserInteractionEvent" + }, + "featureDevEvent": { "shape": "FeatureDevEvent" }, + "featureDevCodeGenerationEvent": { + "shape": "FeatureDevCodeGenerationEvent" + }, + "featureDevCodeAcceptanceEvent": { + "shape": "FeatureDevCodeAcceptanceEvent" + }, + "inlineChatEvent": { "shape": "InlineChatEvent" }, + "transformEvent": { "shape": "TransformEvent" }, + "docGenerationEvent": { "shape": "DocGenerationEvent" }, + "testGenerationEvent": { "shape": "TestGenerationEvent" } }, "union": true }, "TerminalUserInteractionEvent": { "type": "structure", "members": { - "terminalUserInteractionEventType": { "shape": "TerminalUserInteractionEventType" }, + "terminalUserInteractionEventType": { + "shape": "TerminalUserInteractionEventType" + }, "terminal": { "shape": "String" }, "terminalVersion": { "shape": "String" }, "shell": { "shape": "String" }, @@ -1690,25 +3037,122 @@ "type": "string", "enum": ["CODEWHISPERER_TERMINAL_TRANSLATION_ACTION", "CODEWHISPERER_TERMINAL_COMPLETION_INSERTED"] }, + "TestGenerationEvent": { + "type": "structure", + "required": ["jobId", "groupName"], + "members": { + "jobId": { + "shape": "UUID" + }, + "groupName": { + "shape": "TestGenerationJobGroupName" + }, + "timestamp": { + "shape": "Timestamp" + }, + "ideCategory": { + "shape": "IdeCategory" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "numberOfUnitTestCasesGenerated": { + "shape": "Integer" + }, + "numberOfUnitTestCasesAccepted": { + "shape": "Integer" + }, + "linesOfCodeGenerated": { + "shape": "Integer" + }, + "linesOfCodeAccepted": { + "shape": "Integer" + }, + "charsOfCodeGenerated": { + "shape": "Integer" + }, + "charsOfCodeAccepted": { + "shape": "Integer" + } + } + }, + "TestGenerationJob": { + "type": "structure", + "required": ["testGenerationJobId", "testGenerationJobGroupName", "status", "creationTime"], + "members": { + "testGenerationJobId": { + "shape": "UUID" + }, + "testGenerationJobGroupName": { + "shape": "TestGenerationJobGroupName" + }, + "status": { + "shape": "TestGenerationJobStatus" + }, + "shortAnswer": { + "shape": "SensitiveString" + }, + "creationTime": { + "shape": "Timestamp" + }, + "progressRate": { + "shape": "TestGenerationJobProgressRateInteger" + } + } + }, + "TestGenerationJobGroupName": { + "type": "string", + "max": 128, + "min": 1, + "pattern": "[a-zA-Z0-9-_]+" + }, + "TestGenerationJobProgressRateInteger": { + "type": "integer", + "box": true, + "max": 100, + "min": 0 + }, + "TestGenerationJobStatus": { + "type": "string", + "enum": ["IN_PROGRESS", "FAILED", "COMPLETED"] + }, "TextDocument": { "type": "structure", "required": ["relativeFilePath"], "members": { - "relativeFilePath": { "shape": "TextDocumentRelativeFilePathString" }, - "programmingLanguage": { "shape": "ProgrammingLanguage" }, - "text": { "shape": "TextDocumentTextString" }, - "documentSymbols": { "shape": "DocumentSymbols" } + "relativeFilePath": { + "shape": "TextDocumentRelativeFilePathString" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "text": { + "shape": "TextDocumentTextString" + }, + "documentSymbols": { + "shape": "DocumentSymbols" + } } }, "TextDocumentDiagnostic": { "type": "structure", "required": ["document", "range", "source", "severity", "message"], "members": { - "document": { "shape": "TextDocument" }, - "range": { "shape": "Range" }, - "source": { "shape": "SensitiveString" }, - "severity": { "shape": "DiagnosticSeverity" }, - "message": { "shape": "TextDocumentDiagnosticMessageString" } + "document": { + "shape": "TextDocument" + }, + "range": { + "shape": "Range" + }, + "source": { + "shape": "SensitiveString" + }, + "severity": { + "shape": "DiagnosticSeverity" + }, + "message": { + "shape": "TextDocumentDiagnosticMessageString" + } } }, "TextDocumentDiagnosticMessageString": { @@ -1733,55 +3177,67 @@ "type": "structure", "required": ["message"], "members": { - "message": { "shape": "String" } + "message": { + "shape": "String" + } }, "exception": true, - "retryable": { "throttling": true } + "retryable": { + "throttling": true + } + }, + "Timestamp": { + "type": "timestamp" + }, + "TransformEvent": { + "type": "structure", + "required": ["jobId"], + "members": { + "jobId": { + "shape": "TransformationJobId" + }, + "timestamp": { + "shape": "Timestamp" + }, + "ideCategory": { + "shape": "IdeCategory" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "linesOfCodeChanged": { + "shape": "Integer" + }, + "charsOfCodeChanged": { + "shape": "Integer" + }, + "linesOfCodeSubmitted": { + "shape": "Integer" + } + } }, - "Timestamp": { "type": "timestamp" }, "TransformationDotNetRuntimeEnv": { "type": "string", - "enum": [ - "NET_FRAMEWORK_V_3_5", - "NET_FRAMEWORK_V_4_0", - "NET_FRAMEWORK_V_4_5", - "NET_FRAMEWORK_V_4_5_1", - "NET_FRAMEWORK_V_4_5_2", - "NET_FRAMEWORK_V_4_6", - "NET_FRAMEWORK_V_4_6_1", - "NET_FRAMEWORK_V_4_6_2", - "NET_FRAMEWORK_V_4_7", - "NET_FRAMEWORK_V_4_7_1", - "NET_FRAMEWORK_V_4_7_2", - "NET_FRAMEWORK_V_4_8", - "NET_FRAMEWORK_V_4_8_1", - "NET_CORE_APP_1_0", - "NET_CORE_APP_1_1", - "NET_CORE_APP_2_0", - "NET_CORE_APP_2_1", - "NET_CORE_APP_2_2", - "NET_CORE_APP_3_0", - "NET_CORE_APP_3_1", - "NET_5_0", - "NET_6_0", - "NET_7_0", - "NET_8_0" - ] + "enum": ["NET_5_0", "NET_6_0", "NET_7_0", "NET_8_0", "NET_9_0", "NET_STANDARD_2_0"] }, "TransformationDownloadArtifact": { "type": "structure", "members": { - "downloadArtifactType": { "shape": "TransformationDownloadArtifactType" }, + "downloadArtifactType": { + "shape": "TransformationDownloadArtifactType" + }, "downloadArtifactId": { "shape": "ArtifactId" } } }, "TransformationDownloadArtifactType": { "type": "string", - "enum": ["ClientInstructions", "Logs"] + "enum": ["ClientInstructions", "Logs", "GeneratedCode"] }, "TransformationDownloadArtifacts": { "type": "list", - "member": { "shape": "TransformationDownloadArtifact" }, + "member": { + "shape": "TransformationDownloadArtifact" + }, "max": 10, "min": 0 }, @@ -1792,13 +3248,27 @@ "TransformationJob": { "type": "structure", "members": { - "jobId": { "shape": "TransformationJobId" }, - "transformationSpec": { "shape": "TransformationSpec" }, - "status": { "shape": "TransformationStatus" }, - "reason": { "shape": "String" }, - "creationTime": { "shape": "Timestamp" }, - "startExecutionTime": { "shape": "Timestamp" }, - "endExecutionTime": { "shape": "Timestamp" } + "jobId": { + "shape": "TransformationJobId" + }, + "transformationSpec": { + "shape": "TransformationSpec" + }, + "status": { + "shape": "TransformationStatus" + }, + "reason": { + "shape": "String" + }, + "creationTime": { + "shape": "Timestamp" + }, + "startExecutionTime": { + "shape": "Timestamp" + }, + "endExecutionTime": { + "shape": "Timestamp" + } } }, "TransformationJobId": { @@ -1808,7 +3278,17 @@ }, "TransformationLanguage": { "type": "string", - "enum": ["JAVA_8", "JAVA_11", "JAVA_17", "C_SHARP"] + "enum": ["JAVA_8", "JAVA_11", "JAVA_17", "C_SHARP", "COBOL", "PL_I", "JCL"] + }, + "TransformationLanguages": { + "type": "list", + "member": { + "shape": "TransformationLanguage" + } + }, + "TransformationMainframeRuntimeEnv": { + "type": "string", + "enum": ["MAINFRAME"] }, "TransformationOperatingSystemFamily": { "type": "string", @@ -1818,53 +3298,105 @@ "type": "structure", "required": ["transformationSteps"], "members": { - "transformationSteps": { "shape": "TransformationSteps" } + "transformationSteps": { + "shape": "TransformationSteps" + } } }, "TransformationPlatformConfig": { "type": "structure", "members": { - "operatingSystemFamily": { "shape": "TransformationOperatingSystemFamily" } + "operatingSystemFamily": { + "shape": "TransformationOperatingSystemFamily" + } } }, "TransformationProgressUpdate": { "type": "structure", "required": ["name", "status"], "members": { - "name": { "shape": "String" }, - "status": { "shape": "TransformationProgressUpdateStatus" }, - "description": { "shape": "String" }, - "startTime": { "shape": "Timestamp" }, - "endTime": { "shape": "Timestamp" }, - "downloadArtifacts": { "shape": "TransformationDownloadArtifacts" } + "name": { + "shape": "String" + }, + "status": { + "shape": "TransformationProgressUpdateStatus" + }, + "description": { + "shape": "String" + }, + "startTime": { + "shape": "Timestamp" + }, + "endTime": { + "shape": "Timestamp" + }, + "downloadArtifacts": { + "shape": "TransformationDownloadArtifacts" + } } }, "TransformationProgressUpdateStatus": { "type": "string", - "enum": ["IN_PROGRESS", "COMPLETED", "FAILED", "PAUSED"] + "enum": ["IN_PROGRESS", "COMPLETED", "FAILED", "PAUSED", "AWAITING_CLIENT_ACTION", "SKIPPED"] + }, + "TransformationProjectArtifactDescriptor": { + "type": "structure", + "members": { + "sourceCodeArtifact": { + "shape": "TransformationSourceCodeArtifactDescriptor" + } + }, + "union": true }, "TransformationProjectState": { "type": "structure", "members": { "language": { "shape": "TransformationLanguage" }, "runtimeEnv": { "shape": "TransformationRuntimeEnv" }, - "platformConfig": { "shape": "TransformationPlatformConfig" } + "platformConfig": { "shape": "TransformationPlatformConfig" }, + "projectArtifact": { + "shape": "TransformationProjectArtifactDescriptor" + } } }, "TransformationRuntimeEnv": { "type": "structure", "members": { - "java": { "shape": "TransformationJavaRuntimeEnv" }, - "dotNet": { "shape": "TransformationDotNetRuntimeEnv" } + "java": { + "shape": "TransformationJavaRuntimeEnv" + }, + "dotNet": { + "shape": "TransformationDotNetRuntimeEnv" + }, + "mainframe": { + "shape": "TransformationMainframeRuntimeEnv" + } }, "union": true }, + "TransformationSourceCodeArtifactDescriptor": { + "type": "structure", + "members": { + "languages": { + "shape": "TransformationLanguages" + }, + "runtimeEnv": { + "shape": "TransformationRuntimeEnv" + } + } + }, "TransformationSpec": { "type": "structure", "members": { - "transformationType": { "shape": "TransformationType" }, - "source": { "shape": "TransformationProjectState" }, - "target": { "shape": "TransformationProjectState" } + "transformationType": { + "shape": "TransformationType" + }, + "source": { + "shape": "TransformationProjectState" + }, + "target": { + "shape": "TransformationProjectState" + } } }, "TransformationStatus": { @@ -1893,37 +3425,57 @@ "type": "structure", "required": ["id", "name", "description", "status"], "members": { - "id": { "shape": "StepId" }, - "name": { "shape": "String" }, - "description": { "shape": "String" }, - "status": { "shape": "TransformationStepStatus" }, - "progressUpdates": { "shape": "ProgressUpdates" }, - "startTime": { "shape": "Timestamp" }, - "endTime": { "shape": "Timestamp" } + "id": { + "shape": "StepId" + }, + "name": { + "shape": "String" + }, + "description": { + "shape": "String" + }, + "status": { + "shape": "TransformationStepStatus" + }, + "progressUpdates": { + "shape": "ProgressUpdates" + }, + "startTime": { + "shape": "Timestamp" + }, + "endTime": { + "shape": "Timestamp" + } } }, "TransformationStepStatus": { "type": "string", - "enum": ["CREATED", "COMPLETED", "PARTIALLY_COMPLETED", "STOPPED", "FAILED", "PAUSED"] + "enum": ["CREATED", "COMPLETED", "PARTIALLY_COMPLETED", "STOPPED", "FAILED", "PAUSED", "SKIPPED"] }, "TransformationSteps": { "type": "list", - "member": { "shape": "TransformationStep" } + "member": { + "shape": "TransformationStep" + } }, "TransformationType": { "type": "string", - "enum": ["LANGUAGE_UPGRADE"] + "enum": ["LANGUAGE_UPGRADE", "DOCUMENT_GENERATION"] }, "TransformationUploadArtifactType": { "type": "string", - "enum": ["Dependencies"] + "enum": ["Dependencies", "ClientBuildResult"] }, "TransformationUploadContext": { "type": "structure", "required": ["jobId", "uploadArtifactType"], "members": { - "jobId": { "shape": "TransformationJobId" }, - "uploadArtifactType": { "shape": "TransformationUploadArtifactType" } + "jobId": { + "shape": "TransformationJobId" + }, + "uploadArtifactType": { + "shape": "TransformationUploadArtifactType" + } } }, "TransformationUserActionStatus": { @@ -1938,9 +3490,14 @@ "UploadContext": { "type": "structure", "members": { - "taskAssistPlanningUploadContext": { "shape": "TaskAssistPlanningUploadContext" }, - "transformationUploadContext": { "shape": "TransformationUploadContext" }, - "codeAnalysisUploadContext": { "shape": "CodeAnalysisUploadContext" } + "taskAssistPlanningUploadContext": { + "shape": "TaskAssistPlanningUploadContext" + }, + "transformationUploadContext": { + "shape": "TransformationUploadContext" + }, + "codeAnalysisUploadContext": { "shape": "CodeAnalysisUploadContext" }, + "codeFixUploadContext": { "shape": "CodeFixUploadContext" } }, "union": true }, @@ -1955,18 +3512,30 @@ "TRANSFORMATION", "TASK_ASSIST_PLANNING", "AUTOMATIC_FILE_SECURITY_SCAN", - "FULL_PROJECT_SECURITY_SCAN" + "FULL_PROJECT_SECURITY_SCAN", + "UNIT_TESTS_GENERATION", + "CODE_FIX_GENERATION" ] }, "UserContext": { "type": "structure", "required": ["ideCategory", "operatingSystem", "product"], "members": { - "ideCategory": { "shape": "IdeCategory" }, - "operatingSystem": { "shape": "OperatingSystem" }, - "product": { "shape": "UserContextProductString" }, - "clientId": { "shape": "UUID" }, - "ideVersion": { "shape": "String" } + "ideCategory": { + "shape": "IdeCategory" + }, + "operatingSystem": { + "shape": "OperatingSystem" + }, + "product": { + "shape": "UserContextProductString" + }, + "clientId": { + "shape": "UUID" + }, + "ideVersion": { + "shape": "String" + } } }, "UserContextProductString": { @@ -1979,9 +3548,15 @@ "type": "structure", "required": ["content"], "members": { - "content": { "shape": "UserInputMessageContentString" }, - "userInputMessageContext": { "shape": "UserInputMessageContext" }, - "userIntent": { "shape": "UserIntent" } + "content": { + "shape": "UserInputMessageContentString" + }, + "userInputMessageContext": { + "shape": "UserInputMessageContext" + }, + "userIntent": { + "shape": "UserIntent" + } } }, "UserInputMessageContentString": { @@ -1993,12 +3568,30 @@ "UserInputMessageContext": { "type": "structure", "members": { - "editorState": { "shape": "EditorState" }, - "shellState": { "shape": "ShellState" }, - "gitState": { "shape": "GitState" }, - "envState": { "shape": "EnvState" }, - "appStudioContext": { "shape": "AppStudioState" }, - "diagnostic": { "shape": "Diagnostic" } + "editorState": { + "shape": "EditorState" + }, + "shellState": { + "shape": "ShellState" + }, + "gitState": { + "shape": "GitState" + }, + "envState": { + "shape": "EnvState" + }, + "appStudioContext": { + "shape": "AppStudioState" + }, + "diagnostic": { + "shape": "Diagnostic" + }, + "consoleState": { + "shape": "ConsoleState" + }, + "userSettings": { + "shape": "UserSettings" + } } }, "UserIntent": { @@ -2011,19 +3604,55 @@ "CITE_SOURCES", "EXPLAIN_LINE_BY_LINE", "EXPLAIN_CODE_SELECTION", - "GENERATE_CLOUDFORMATION_TEMPLATE" + "GENERATE_CLOUDFORMATION_TEMPLATE", + "GENERATE_UNIT_TESTS", + "CODE_GENERATION" ] }, "UserModificationEvent": { "type": "structure", - "required": ["sessionId", "requestId", "programmingLanguage", "modificationPercentage", "timestamp"], + "required": [ + "sessionId", + "requestId", + "programmingLanguage", + "modificationPercentage", + "timestamp", + "acceptedCharacterCount", + "unmodifiedAcceptedCharacterCount" + ], "members": { - "sessionId": { "shape": "UUID" }, - "requestId": { "shape": "UUID" }, - "programmingLanguage": { "shape": "ProgrammingLanguage" }, - "modificationPercentage": { "shape": "Double" }, - "customizationArn": { "shape": "CustomizationArn" }, - "timestamp": { "shape": "Timestamp" } + "sessionId": { + "shape": "UUID" + }, + "requestId": { + "shape": "UUID" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "modificationPercentage": { + "shape": "Double" + }, + "customizationArn": { + "shape": "CustomizationArn" + }, + "timestamp": { + "shape": "Timestamp" + }, + "acceptedCharacterCount": { + "shape": "PrimitiveInteger" + }, + "unmodifiedAcceptedCharacterCount": { + "shape": "PrimitiveInteger" + } + } + }, + "UserSettings": { + "type": "structure", + "members": { + "hasConsentedToCrossRegionCalls": { + "shape": "Boolean" + } } }, "UserTriggerDecisionEvent": { @@ -2038,45 +3667,87 @@ "timestamp" ], "members": { - "sessionId": { "shape": "UUID" }, - "requestId": { "shape": "UUID" }, - "customizationArn": { "shape": "CustomizationArn" }, - "programmingLanguage": { "shape": "ProgrammingLanguage" }, - "completionType": { "shape": "CompletionType" }, - "suggestionState": { "shape": "SuggestionState" }, - "recommendationLatencyMilliseconds": { "shape": "Double" }, - "timestamp": { "shape": "Timestamp" }, - "triggerToResponseLatencyMilliseconds": { "shape": "Double" }, - "suggestionReferenceCount": { "shape": "PrimitiveInteger" }, - "generatedLine": { "shape": "PrimitiveInteger" }, - "numberOfRecommendations": { "shape": "PrimitiveInteger" } + "sessionId": { + "shape": "UUID" + }, + "requestId": { + "shape": "UUID" + }, + "customizationArn": { + "shape": "CustomizationArn" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "completionType": { + "shape": "CompletionType" + }, + "suggestionState": { + "shape": "SuggestionState" + }, + "recommendationLatencyMilliseconds": { + "shape": "Double" + }, + "timestamp": { + "shape": "Timestamp" + }, + "triggerToResponseLatencyMilliseconds": { + "shape": "Double" + }, + "suggestionReferenceCount": { + "shape": "PrimitiveInteger" + }, + "generatedLine": { + "shape": "PrimitiveInteger" + }, + "numberOfRecommendations": { + "shape": "PrimitiveInteger" + }, + "perceivedLatencyMilliseconds": { + "shape": "Double" + }, + "acceptedCharacterCount": { + "shape": "PrimitiveInteger" + } } }, "ValidationException": { "type": "structure", "required": ["message"], "members": { - "message": { "shape": "String" }, - "reason": { "shape": "ValidationExceptionReason" } + "message": { + "shape": "String" + }, + "reason": { + "shape": "ValidationExceptionReason" + } }, "exception": true }, "ValidationExceptionReason": { "type": "string", - "enum": ["INVALID_CONVERSATION_ID", "CONTENT_LENGTH_EXCEEDS_THRESHOLD"] + "enum": ["INVALID_CONVERSATION_ID", "CONTENT_LENGTH_EXCEEDS_THRESHOLD", "INVALID_KMS_GRANT"] }, "WorkspaceState": { "type": "structure", "required": ["uploadId", "programmingLanguage"], "members": { - "uploadId": { "shape": "UploadId" }, - "programmingLanguage": { "shape": "ProgrammingLanguage" }, - "contextTruncationScheme": { "shape": "ContextTruncationScheme" } + "uploadId": { + "shape": "UploadId" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage" + }, + "contextTruncationScheme": { + "shape": "ContextTruncationScheme" + } } }, "timeBetweenChunks": { "type": "list", - "member": { "shape": "Double" }, + "member": { + "shape": "Double" + }, "max": 100, "min": 0 } diff --git a/packages/core/src/amazonqFeatureDev/client/featureDev.ts b/packages/core/src/amazonqFeatureDev/client/featureDev.ts index 9cb541ee357..ceef0b616e7 100644 --- a/packages/core/src/amazonqFeatureDev/client/featureDev.ts +++ b/packages/core/src/amazonqFeatureDev/client/featureDev.ts @@ -10,7 +10,6 @@ import { ServiceOptions } from '../../shared/awsClientBuilder' import globals from '../../shared/extensionGlobals' import { getLogger } from '../../shared/logger' import * as FeatureDevProxyClient from './featuredevproxyclient' -import apiConfig = require('./codewhispererruntime-2022-11-11.json') import { featureName } from '../constants' import { CodeReference } from '../../amazonq/webview/ui/connector' import { @@ -25,9 +24,25 @@ import { getCodewhispererConfig } from '../../codewhisperer/client/codewhisperer import { createCodeWhispererChatStreamingClient } from '../../shared/clients/codewhispererChatClient' import { getClientId, getOptOutPreference, getOperatingSystem } from '../../shared/telemetry/util' import { extensionVersion } from '../../shared/vscode/env' +import apiConfig = require('./codewhispererruntime-2022-11-11.json') +import { + FeatureDevCodeAcceptanceEvent, + FeatureDevCodeGenerationEvent, + MetricData, + TelemetryEvent, +} from './featuredevproxyclient' + +// Re-enable once BE is able to handle retries. +const writeAPIRetryOptions = { + maxRetries: 0, + retryDelayOptions: { + // The default number of milliseconds to use in the exponential backoff + base: 500, + }, +} // Create a client for featureDev proxy client based off of aws sdk v2 -export async function createFeatureDevProxyClient(): Promise { +export async function createFeatureDevProxyClient(options?: Partial): Promise { const bearerToken = await AuthUtil.instance.getBearerToken() const cwsprConfig = getCodewhispererConfig() return (await globals.sdkClientBuilder.createAwsService( @@ -37,27 +52,25 @@ export async function createFeatureDevProxyClient(): Promise) { // Should not be stored for the whole session. // Client has to be reinitialized for each request so we always have a fresh bearerToken - return await createFeatureDevProxyClient() + return await createFeatureDevProxyClient(options) } public async createConversation() { try { - const client = await this.getClient() + const client = await this.getClient(writeAPIRetryOptions) getLogger().debug(`Executing createTaskAssistConversation with {}`) const { conversationId, $response } = await client.createTaskAssistConversation().promise() getLogger().debug(`${featureName}: Created conversation: %O`, { @@ -70,7 +83,11 @@ export class FeatureDevClient { getLogger().error( `${featureName}: failed to start conversation: ${e.message} RequestId: ${e.requestId}` ) - if (e.code === 'ServiceQuotaExceededException') { + // BE service will throw ServiceQuota if conversation limit is reached. API Front-end will throw Throttling with this message if conversation limit is reached + if ( + e.code === 'ServiceQuotaExceededException' || + (e.code === 'ThrottlingException' && e.message.includes('reached for this month.')) + ) { throw new MonthlyConversationLimitError(e.message) } throw new ApiError(e.message, 'CreateConversation', e.code, e.statusCode ?? 400) @@ -80,15 +97,21 @@ export class FeatureDevClient { } } - public async createUploadUrl(conversationId: string, contentChecksumSha256: string, contentLength: number) { + public async createUploadUrl( + conversationId: string, + contentChecksumSha256: string, + contentLength: number, + uploadId: string + ) { try { - const client = await this.getClient() + const client = await this.getClient(writeAPIRetryOptions) const params = { uploadContext: { taskAssistPlanningUploadContext: { conversationId, }, }, + uploadId, contentChecksum: contentChecksumSha256, contentChecksumType: 'SHA_256', artifactType: 'SourceCode', @@ -98,7 +121,7 @@ export class FeatureDevClient { getLogger().debug(`Executing createUploadUrl with %O`, omit(params, 'contentChecksum')) const response = await client.createUploadUrl(params).promise() getLogger().debug(`${featureName}: Created upload url: %O`, { - uploadId: response.uploadId, + uploadId: uploadId, requestId: response.$response.requestId, }) return response @@ -117,10 +140,19 @@ export class FeatureDevClient { } } - public async startCodeGeneration(conversationId: string, uploadId: string, message: string) { + public async startCodeGeneration( + conversationId: string, + uploadId: string, + message: string, + intent: FeatureDevProxyClient.Intent, + codeGenerationId: string, + currentCodeGenerationId?: string, + intentContext?: FeatureDevProxyClient.IntentContext + ) { try { - const client = await this.getClient() + const client = await this.getClient(writeAPIRetryOptions) const params = { + codeGenerationId, conversationState: { conversationId, currentMessage: { @@ -132,6 +164,13 @@ export class FeatureDevClient { uploadId, programmingLanguage: { languageName: 'javascript' }, }, + intent, + } as FeatureDevProxyClient.Types.StartTaskAssistCodeGenerationRequest + if (currentCodeGenerationId) { + params.currentCodeGenerationId = currentCodeGenerationId + } + if (intentContext) { + params.intentContext = intentContext } getLogger().debug(`Executing startTaskAssistCodeGeneration with %O`, params) const response = await client.startTaskAssistCodeGeneration(params).promise() @@ -143,13 +182,22 @@ export class FeatureDevClient { (e as any).requestId }` ) - if ( - isAwsError(e) && - ((e.code === 'ThrottlingException' && - e.message.includes('limit for number of iterations on a code generation')) || - e.code === 'ServiceQuotaExceededException') - ) { - throw new CodeIterationLimitError() + if (isAwsError(e)) { + // API Front-end will throw Throttling if conversation limit is reached. API Front-end monitors StartCodeGeneration for throttling + if ( + e.code === 'ThrottlingException' && + e.message.includes('StartTaskAssistCodeGeneration reached for this month.') + ) { + throw new MonthlyConversationLimitError(e.message) + } + // BE service will throw ServiceQuota if code generation iteration limit is reached + else if ( + e.code === 'ServiceQuotaExceededException' || + (e.code === 'ThrottlingException' && + e.message.includes('limit for number of iterations on a code generation')) + ) { + throw new CodeIterationLimitError() + } } throw new ToolkitError((e as Error).message, { code: 'StartCodeGenerationFailed' }) } @@ -237,13 +285,39 @@ export class FeatureDevClient { * @param conversationId */ public async sendFeatureDevTelemetryEvent(conversationId: string) { + await this.sendFeatureDevEvent('featureDevEvent', { + conversationId, + }) + } + + public async sendFeatureDevCodeGenerationEvent(event: FeatureDevCodeGenerationEvent) { + getLogger().debug( + `featureDevCodeGenerationEvent: conversationId: ${event.conversationId} charactersOfCodeGenerated: ${event.charactersOfCodeGenerated} linesOfCodeGenerated: ${event.linesOfCodeGenerated}` + ) + await this.sendFeatureDevEvent('featureDevCodeGenerationEvent', event) + } + + public async sendFeatureDevCodeAcceptanceEvent(event: FeatureDevCodeAcceptanceEvent) { + getLogger().debug( + `featureDevCodeAcceptanceEvent: conversationId: ${event.conversationId} charactersOfCodeAccepted: ${event.charactersOfCodeAccepted} linesOfCodeAccepted: ${event.linesOfCodeAccepted}` + ) + await this.sendFeatureDevEvent('featureDevCodeAcceptanceEvent', event) + } + + public async sendMetricData(event: MetricData) { + getLogger().debug(`featureDevCodeGenerationMetricData: dimensions: ${event.dimensions}`) + await this.sendFeatureDevEvent('metricData', event) + } + + public async sendFeatureDevEvent( + eventName: T, + event: NonNullable + ) { try { const client = await this.getClient() const params: FeatureDevProxyClient.SendTelemetryEventRequest = { telemetryEvent: { - featureDevEvent: { - conversationId, - }, + [eventName]: event, }, optOutPreference: getOptOutPreference(), userContext: { @@ -256,11 +330,11 @@ export class FeatureDevClient { } const response = await client.sendTelemetryEvent(params).promise() getLogger().debug( - `${featureName}: successfully sent featureDevEvent: ConversationId: ${conversationId} RequestId: ${response.$response.requestId}` + `${featureName}: successfully sent ${eventName} telemetryEvent:${'conversationId' in event ? ' ConversationId: ' + event.conversationId : ''} RequestId: ${response.$response.requestId}` ) } catch (e) { getLogger().error( - `${featureName}: failed to send feature dev telemetry: ${(e as Error).name}: ${ + `${featureName}: failed to send ${eventName} telemetry: ${(e as Error).name}: ${ (e as Error).message } RequestId: ${(e as any).requestId}` ) diff --git a/packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts b/packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts index cbd6a2296f0..f24ddb4e923 100644 --- a/packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts +++ b/packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ChatItemAction, MynahIcons } from '@aws/mynah-ui' +import { MynahIcons } from '@aws/mynah-ui' import * as path from 'path' import * as vscode from 'vscode' import { EventEmitter } from 'vscode' @@ -12,25 +12,25 @@ import { createSingleFileDialog } from '../../../shared/ui/common/openDialog' import { CodeIterationLimitError, ContentLengthError, + createUserFacingErrorMessage, + denyListedErrors, FeatureDevServiceError, MonthlyConversationLimitError, + NoChangeRequiredException, PrepareRepoFailedError, PromptRefusalException, SelectedFolderNotInWorkspaceFolderError, TabIdNotFoundError, UploadCodeError, + UploadURLExpired, UserMessageNotFoundError, WorkspaceFolderNotFoundError, ZipFileError, - createUserFacingErrorMessage, - denyListedErrors, } from '../../errors' import { codeGenRetryLimit, defaultRetryLimit } from '../../limits' import { Session } from '../../session/session' -import { featureName } from '../../constants' -import { ChatSessionStorage } from '../../storages/chatSession' -import { DevPhase, FollowUpTypes, SessionStatePhase } from '../../types' -import { Messenger } from './messenger/messenger' +import { featureDevScheme, featureName } from '../../constants' +import { DeletedFileInfo, DevPhase, MetricDataOperationName, MetricDataResult, type NewFileInfo } from '../../types' import { AuthUtil } from '../../../codewhisperer/util/authUtil' import { AuthController } from '../../../amazonq/auth/controller' import { getLogger } from '../../../shared/logger' @@ -44,6 +44,12 @@ import { getWorkspaceFoldersByPrefixes } from '../../../shared/utilities/workspa import { openDeletedDiff, openDiff } from '../../../amazonq/commons/diff' import { i18n } from '../../../shared/i18n-helper' import globals from '../../../shared/extensionGlobals' +import { randomUUID } from '../../../shared' +import { FollowUpTypes } from '../../../amazonq/commons/types' +import { Messenger } from '../../../amazonq/commons/connector/baseMessenger' +import { BaseChatSessionStorage } from '../../../amazonq/commons/baseChatStorage' + +export const TotalSteps = 3 export interface ChatControllerEventEmitters { readonly processHumanChatMessage: EventEmitter @@ -58,6 +64,7 @@ export interface ChatControllerEventEmitters { readonly processResponseBodyLinkClick: EventEmitter readonly insertCodeAtPositionClicked: EventEmitter readonly fileClicked: EventEmitter + readonly storeCodeResultMessageId: EventEmitter } type OpenDiffMessage = { @@ -66,6 +73,7 @@ type OpenDiffMessage = { // currently the zip file path filePath: string deleted: boolean + codeGenerationId: string } type fileClickedMessage = { @@ -74,9 +82,16 @@ type fileClickedMessage = { filePath: string actionName: string } + +type StoreMessageIdMessage = { + tabID: string + messageId: string +} + export class FeatureDevController { + private readonly scheme: string = featureDevScheme private readonly messenger: Messenger - private readonly sessionStorage: ChatSessionStorage + private readonly sessionStorage: BaseChatSessionStorage private isAmazonQVisible: boolean private authController: AuthController private contentController: EditorContentController @@ -84,7 +99,7 @@ export class FeatureDevController { public constructor( private readonly chatControllerMessageListeners: ChatControllerEventEmitters, messenger: Messenger, - sessionStorage: ChatSessionStorage, + sessionStorage: BaseChatSessionStorage, onDidChangeAmazonQVisibility: vscode.Event ) { this.messenger = messenger @@ -131,6 +146,11 @@ export class FeatureDevController { this.initialExamples(data) break case FollowUpTypes.NewTask: + this.messenger.sendAnswer({ + type: 'answer', + tabID: data?.tabID, + message: i18n('AWS.amazonq.featureDev.answer.newTaskChanges'), + }) return this.newTask(data) case FollowUpTypes.CloseSession: return this.closeSession(data) @@ -163,6 +183,9 @@ export class FeatureDevController { this.chatControllerMessageListeners.fileClicked.event(async (data) => { return await this.fileClicked(data) }) + this.chatControllerMessageListeners.storeCodeResultMessageId.event(async (data) => { + return await this.storeCodeResultMessageId(data) + }) } private async processChatItemVotedMessage(tabId: string, vote: string) { @@ -206,14 +229,15 @@ export class FeatureDevController { ) let defaultMessage - const isDenyListedError = denyListedErrors.some((err) => errorMessage.includes(err)) + const isDenyListedError = denyListedErrors.some((denyListedError) => err.message.includes(denyListedError)) - switch (err.code) { - case ContentLengthError.errorName: + switch (err.constructor.name) { + case ContentLengthError.name: this.messenger.sendAnswer({ type: 'answer', tabID: message.tabID, message: err.message + messageWithConversationId(session?.conversationIdUnsafe), + canBeVoted: true, }) this.messenger.sendAnswer({ type: 'system-prompt', @@ -227,14 +251,14 @@ export class FeatureDevController { ], }) break - case MonthlyConversationLimitError.errorName: + case MonthlyConversationLimitError.name: this.messenger.sendMonthlyLimitError(message.tabID) break - case FeatureDevServiceError.errorName: - case UploadCodeError.errorName: - case UserMessageNotFoundError.errorName: - case TabIdNotFoundError.errorName: - case PrepareRepoFailedError.errorName: + case FeatureDevServiceError.name: + case UploadCodeError.name: + case UserMessageNotFoundError.name: + case TabIdNotFoundError.name: + case PrepareRepoFailedError.name: this.messenger.sendErrorMessage( errorMessage, message.tabID, @@ -242,22 +266,36 @@ export class FeatureDevController { session?.conversationIdUnsafe ) break - case PromptRefusalException.errorName: - case ZipFileError.errorName: + case PromptRefusalException.name: + case ZipFileError.name: this.messenger.sendErrorMessage(errorMessage, message.tabID, 0, session?.conversationIdUnsafe, true) break - case CodeIterationLimitError.errorName: + case NoChangeRequiredException.name: + this.messenger.sendAnswer({ + type: 'answer', + tabID: message.tabID, + message: err.message, + canBeVoted: true, + }) + // Allow users to re-work the task description. + return this.newTask(message) + case CodeIterationLimitError.name: this.messenger.sendAnswer({ type: 'answer', tabID: message.tabID, message: err.message + messageWithConversationId(session?.conversationIdUnsafe), + canBeVoted: true, }) this.messenger.sendAnswer({ type: 'system-prompt', tabID: message.tabID, followUps: [ { - pillText: i18n('AWS.amazonq.featureDev.pillText.insertCode'), + pillText: + session?.getInsertCodePillText([ + ...(session?.state.filePaths ?? []), + ...(session?.state.deletedFiles ?? []), + ]) ?? i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges'), type: FollowUpTypes.InsertCode, icon: 'ok' as MynahIcons, status: 'success', @@ -265,6 +303,14 @@ export class FeatureDevController { ], }) break + case UploadURLExpired.name: + this.messenger.sendAnswer({ + type: 'answer', + tabID: message.tabID, + message: err.message, + canBeVoted: true, + }) + break default: if (isDenyListedError || this.retriesRemaining(session) === 0) { defaultMessage = i18n('AWS.amazonq.featureDev.error.codeGen.denyListedError') @@ -284,6 +330,25 @@ export class FeatureDevController { } } + /** + * + * This function dispose cancellation token to free resources and provide a new token. + * Since user can abort a call in the same session, when the processing ends, we need provide a new one + * to start with the new prompt and allow the ability to stop again. + * + * @param session + */ + + private disposeToken(session: Session | undefined) { + if (session?.state?.tokenSource?.token.isCancellationRequested) { + session?.state.tokenSource?.dispose() + if (session?.state?.tokenSource) { + session.state.tokenSource = new vscode.CancellationTokenSource() + } + getLogger().debug('Request cancelled, skipping further processing') + } + } + // TODO add type private async processUserChatMessage(message: any) { if (message.message === undefined) { @@ -306,6 +371,7 @@ export class FeatureDevController { getLogger().debug(`${featureName}: Processing message: ${message.message}`) session = await this.sessionStorage.getSession(message.tabID) + await session.disableFileList() const authState = await AuthUtil.instance.getChatAuthState() if (authState.amazonQ !== 'connected') { await this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID) @@ -319,7 +385,8 @@ export class FeatureDevController { await this.onCodeGeneration(session, message.message, message.tabID) } } catch (err: any) { - this.processErrorChatMessage(err, message, session) + this.disposeToken(session) + await this.processErrorChatMessage(err, message, session) // Lock the chat input until they explicitly click one of the follow ups this.messenger.sendChatInputEnabled(message.tabID, false) } @@ -346,9 +413,15 @@ export class FeatureDevController { canBeVoted: true, }) this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.pillText.generatingCode')) + await session.sendMetricDataTelemetry(MetricDataOperationName.StartCodeGeneration, MetricDataResult.Success) await session.send(message) const filePaths = session.state.filePaths ?? [] const deletedFiles = session.state.deletedFiles ?? [] + // Only add the follow up accept/deny buttons when the tab hasn't been closed/request hasn't been cancelled + if (session?.state?.tokenSource?.token.isCancellationRequested) { + return + } + if (filePaths.length === 0 && deletedFiles.length === 0) { this.messenger.sendAnswer({ message: i18n('AWS.amazonq.featureDev.pillText.unableGenerateChanges'), @@ -375,17 +448,13 @@ export class FeatureDevController { return } - // Only add the follow up accept/deny buttons when the tab hasn't been closed/request hasn't been cancelled - if (session?.state.tokenSource.token.isCancellationRequested) { - return - } - this.messenger.sendCodeResult( filePaths, deletedFiles, session.state.references ?? [], tabID, - session.uploadId + session.uploadId, + session.state.codeGenerationId ?? '' ) const remainingIterations = session.state.codeGenerationRemainingIterationCount @@ -402,64 +471,111 @@ export class FeatureDevController { }) } - this.messenger.sendAnswer({ - message: undefined, - type: 'system-prompt', - followUps: this.getFollowUpOptions(session?.state.phase), - tabID: tabID, - }) + if (session?.state.phase === DevPhase.CODEGEN) { + const messageId = randomUUID() + session.updateAcceptCodeMessageId(messageId) + session.updateAcceptCodeTelemetrySent(false) + // need to add the followUps with an extra update here, or it will double-render them + this.messenger.sendAnswer({ + message: undefined, + type: 'system-prompt', + followUps: [], + tabID: tabID, + messageId, + }) + await session.updateChatAnswer(tabID, i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges')) + await session.sendLinesOfCodeGeneratedTelemetry() + } this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.pillText.selectOption')) + } catch (err: any) { + getLogger().error(`${featureName}: Error during code generation: ${err}`) + + let result: string + switch (err.constructor.name) { + case FeatureDevServiceError.name: + if (err.code === 'EmptyPatchException') { + result = MetricDataResult.LlmFailure + } else if (err.code === 'GuardrailsException' || err.code === 'ThrottlingException') { + result = MetricDataResult.Error + } else { + result = MetricDataResult.Fault + } + break + case PromptRefusalException.name: + case NoChangeRequiredException.name: + result = MetricDataResult.Error + break + default: + result = MetricDataResult.Fault + break + } + + await session.sendMetricDataTelemetry(MetricDataOperationName.EndCodeGeneration, result) + throw err } finally { // Finish processing the event - this.messenger.sendAsyncEventProgress(tabID, false, undefined) - - // Lock the chat input until they explicitly click one of the follow ups - this.messenger.sendChatInputEnabled(tabID, false) - if (!this.isAmazonQVisible) { - const open = 'Open chat' - const resp = await vscode.window.showInformationMessage( - i18n('AWS.amazonq.featureDev.answer.qGeneratedCode'), - open + if (session?.state?.tokenSource?.token.isCancellationRequested) { + this.workOnNewTask( + session.tabID, + session.state.codeGenerationRemainingIterationCount || + TotalSteps - (session.state?.currentIteration || 0), + session.state.codeGenerationTotalIterationCount || TotalSteps, + session?.state?.tokenSource?.token.isCancellationRequested ) - if (resp === open) { - await vscode.commands.executeCommand('aws.AmazonQChatView.focus') - // TODO add focusing on the specific tab once that's implemented + this.disposeToken(session) + } else { + this.messenger.sendAsyncEventProgress(tabID, false, undefined) + + // Lock the chat input until they explicitly click one of the follow ups + this.messenger.sendChatInputEnabled(tabID, false) + + if (!this.isAmazonQVisible) { + const open = 'Open chat' + const resp = await vscode.window.showInformationMessage( + i18n('AWS.amazonq.featureDev.answer.qGeneratedCode'), + open + ) + if (resp === open) { + await vscode.commands.executeCommand('aws.AmazonQChatView.focus') + // TODO add focusing on the specific tab once that's implemented + } } } } + await session.sendMetricDataTelemetry(MetricDataOperationName.EndCodeGeneration, MetricDataResult.Success) } - // TODO add type - private async insertCode(message: any) { - let session - try { - session = await this.sessionStorage.getSession(message.tabID) - - const acceptedFiles = (paths?: { rejected: boolean }[]) => (paths || []).filter((i) => !i.rejected).length - - const amazonqNumberOfFilesAccepted = - acceptedFiles(session.state.filePaths) + acceptedFiles(session.state.deletedFiles) - - telemetry.amazonq_isAcceptedCodeChanges.emit({ - credentialStartUrl: AuthUtil.instance.startUrl, - amazonqConversationId: session.conversationId, - amazonqNumberOfFilesAccepted, - enabled: true, - result: 'Succeeded', - }) - await session.insertChanges() + private sendUpdateCodeMessage(tabID: string) { + this.messenger.sendAnswer({ + type: 'answer', + tabID, + message: i18n('AWS.amazonq.featureDev.answer.updateCode'), + canBeVoted: true, + }) + } + private workOnNewTask( + tabID: string, + remainingIterations: number = 0, + totalIterations?: number, + isStoppedGeneration: boolean = false + ) { + if (isStoppedGeneration) { this.messenger.sendAnswer({ - type: 'answer', - tabID: message.tabID, - message: i18n('AWS.amazonq.featureDev.answer.updateCode'), - canBeVoted: true, + message: + (remainingIterations ?? 0) <= 0 + ? "I stopped generating your code. You don't have more iterations left, however, you can start a new session." + : `I stopped generating your code. If you want to continue working on this task, provide another description. You have ${remainingIterations} out of ${totalIterations} code generations left.`, + type: 'answer-part', + tabID, }) + } + if ((remainingIterations <= 0 && isStoppedGeneration) || !isStoppedGeneration) { this.messenger.sendAnswer({ type: 'system-prompt', - tabID: message.tabID, + tabID, followUps: [ { pillText: i18n('AWS.amazonq.featureDev.pillText.newTask'), @@ -473,11 +589,37 @@ export class FeatureDevController { }, ], }) + this.messenger.sendChatInputEnabled(tabID, false) + this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.pillText.selectOption')) + return + } - this.messenger.sendUpdatePlaceholder( - message.tabID, - i18n('AWS.amazonq.featureDev.placeholder.additionalImprovements') - ) + // Ensure that chat input is enabled so that they can provide additional iterations if they choose + this.messenger.sendChatInputEnabled(tabID, true) + this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.placeholder.additionalImprovements')) + } + // TODO add type + private async insertCode(message: any) { + let session + try { + session = await this.sessionStorage.getSession(message.tabID) + + const acceptedFiles = (paths?: { rejected: boolean }[]) => (paths || []).filter((i) => !i.rejected).length + + const filesAccepted = acceptedFiles(session.state.filePaths) + acceptedFiles(session.state.deletedFiles) + + this.sendAcceptCodeTelemetry(session, filesAccepted) + + await session.insertChanges() + if (session.acceptCodeMessageId) { + this.sendUpdateCodeMessage(message.tabID) + this.workOnNewTask( + message.tabID, + session.state.codeGenerationRemainingIterationCount, + session.state.codeGenerationTotalIterationCount + ) + await this.clearAcceptCodeMessageId(message.tabID) + } } catch (err: any) { this.messenger.sendErrorMessage( createUserFacingErrorMessage(`Failed to insert code changes: ${err.message}`), @@ -537,28 +679,6 @@ export class FeatureDevController { } } - private getFollowUpOptions(phase: SessionStatePhase | undefined): ChatItemAction[] { - switch (phase) { - case DevPhase.CODEGEN: - return [ - { - pillText: i18n('AWS.amazonq.featureDev.pillText.insertCode'), - type: FollowUpTypes.InsertCode, - icon: 'ok' as MynahIcons, - status: 'success', - }, - { - pillText: i18n('AWS.amazonq.featureDev.pillText.provideFeedback'), - type: FollowUpTypes.ProvideFeedbackAndRegenerateCode, - icon: 'refresh' as MynahIcons, - status: 'info', - }, - ] - default: - return [] - } - } - private async modifyDefaultSourceFolder(message: any) { const session = await this.sessionStorage.getSession(message.tabID) @@ -647,30 +767,72 @@ export class FeatureDevController { const tabId: string = message.tabID const messageId = message.messageId const filePathToUpdate: string = message.filePath + const action = message.actionName const session = await this.sessionStorage.getSession(tabId) const filePathIndex = (session.state.filePaths ?? []).findIndex((obj) => obj.relativePath === filePathToUpdate) - if (filePathIndex !== -1 && session.state.filePaths) { - session.state.filePaths[filePathIndex].rejected = !session.state.filePaths[filePathIndex].rejected - } const deletedFilePathIndex = (session.state.deletedFiles ?? []).findIndex( (obj) => obj.relativePath === filePathToUpdate ) + + if (filePathIndex !== -1 && session.state.filePaths) { + if (action === 'accept-change') { + this.sendAcceptCodeTelemetry(session, 1) + await session.insertNewFiles([session.state.filePaths[filePathIndex]]) + await session.insertCodeReferenceLogs(session.state.references ?? []) + await this.openFile(session.state.filePaths[filePathIndex], tabId) + } else { + session.state.filePaths[filePathIndex].rejected = !session.state.filePaths[filePathIndex].rejected + } + } if (deletedFilePathIndex !== -1 && session.state.deletedFiles) { - session.state.deletedFiles[deletedFilePathIndex].rejected = - !session.state.deletedFiles[deletedFilePathIndex].rejected + if (action === 'accept-change') { + this.sendAcceptCodeTelemetry(session, 1) + await session.applyDeleteFiles([session.state.deletedFiles[deletedFilePathIndex]]) + await session.insertCodeReferenceLogs(session.state.references ?? []) + } else { + session.state.deletedFiles[deletedFilePathIndex].rejected = + !session.state.deletedFiles[deletedFilePathIndex].rejected + } } - await session.updateFilesPaths( - tabId, - session.state.filePaths ?? [], - session.state.deletedFiles ?? [], - messageId - ) + await session.updateFilesPaths({ + tabID: tabId, + filePaths: session.state.filePaths ?? [], + deletedFiles: session.state.deletedFiles ?? [], + messageId, + }) + + if (session.acceptCodeMessageId) { + const allFilePathsAccepted = session.state.filePaths?.every( + (filePath: NewFileInfo) => !filePath.rejected && filePath.changeApplied + ) + const allDeletedFilePathsAccepted = session.state.deletedFiles?.every( + (filePath: DeletedFileInfo) => !filePath.rejected && filePath.changeApplied + ) + if (allFilePathsAccepted && allDeletedFilePathsAccepted) { + this.sendUpdateCodeMessage(tabId) + this.workOnNewTask( + tabId, + session.state.codeGenerationRemainingIterationCount, + session.state.codeGenerationTotalIterationCount + ) + await this.clearAcceptCodeMessageId(tabId) + } + } + } + + private async storeCodeResultMessageId(message: StoreMessageIdMessage) { + const tabId: string = message.tabID + const messageId = message.messageId + const session = await this.sessionStorage.getSession(tabId) + + session.updateCodeResultMessageId(messageId) } private async openDiff(message: OpenDiffMessage) { const tabId: string = message.tabID + const codeGenerationId: string = message.messageId const zipFilePath: string = message.filePath const session = await this.sessionStorage.getSession(tabId) telemetry.amazonq_isReviewedChanges.emit({ @@ -685,16 +847,40 @@ export class FeatureDevController { if (message.deleted) { const name = path.basename(pathInfos.relativePath) - await openDeletedDiff(pathInfos.absolutePath, name, tabId) + await openDeletedDiff(pathInfos.absolutePath, name, tabId, this.scheme) } else { - const rightPath = path.join(session.uploadId, zipFilePath) - await openDiff(pathInfos.absolutePath, rightPath, tabId) + let uploadId = session.uploadId + if (session?.state?.uploadHistory && session.state.uploadHistory[codeGenerationId]) { + uploadId = session?.state?.uploadHistory[codeGenerationId].uploadId + } + const rightPath = path.join(uploadId, zipFilePath) + await openDiff(pathInfos.absolutePath, rightPath, tabId, this.scheme) } } + private async openFile(filePath: NewFileInfo, tabId: string) { + const leftPath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath) + const rightPath = filePath.virtualMemoryUri.path + await openDiff(leftPath, rightPath, tabId, this.scheme) + } + private async stopResponse(message: any) { + telemetry.ui_click.emit({ elementId: 'amazonq_stopCodeGeneration' }) + this.messenger.sendAnswer({ + message: i18n('AWS.amazonq.featureDev.pillText.stoppingCodeGeneration'), + type: 'answer-part', + tabID: message.tabID, + }) + this.messenger.sendUpdatePlaceholder( + message.tabID, + i18n('AWS.amazonq.featureDev.pillText.stoppingCodeGeneration') + ) + this.messenger.sendChatInputEnabled(message.tabID, false) + const session = await this.sessionStorage.getSession(message.tabID) - session.state.tokenSource.cancel() + if (session.state?.tokenSource) { + session.state?.tokenSource?.cancel() + } } private async tabOpened(message: any) { @@ -748,6 +934,7 @@ export class FeatureDevController { private async newTask(message: any) { // Old session for the tab is ending, delete it so we can create a new one for the message id const session = await this.sessionStorage.getSession(message.tabID) + await session.disableFileList() telemetry.amazonq_endChat.emit({ amazonqConversationId: session.conversationId, amazonqEndOfTheConversationLatency: performance.now() - session.telemetry.sessionStartTime, @@ -758,11 +945,7 @@ export class FeatureDevController { // Re-run the opening flow, where we check auth + create a session await this.tabOpened(message) - this.messenger.sendAnswer({ - type: 'answer', - tabID: message.tabID, - message: i18n('AWS.amazonq.featureDev.answer.newTaskChanges'), - }) + this.messenger.sendChatInputEnabled(message.tabID, true) this.messenger.sendUpdatePlaceholder(message.tabID, i18n('AWS.amazonq.featureDev.placeholder.describe')) } @@ -776,6 +959,7 @@ export class FeatureDevController { this.messenger.sendChatInputEnabled(message.tabID, false) const session = await this.sessionStorage.getSession(message.tabID) + await session.disableFileList() telemetry.amazonq_endChat.emit({ amazonqConversationId: session.conversationId, amazonqEndOfTheConversationLatency: performance.now() - session.telemetry.sessionStartTime, @@ -798,4 +982,23 @@ export class FeatureDevController { private retriesRemaining(session: Session | undefined) { return session?.retries ?? defaultRetryLimit } + + private async clearAcceptCodeMessageId(tabID: string) { + const session = await this.sessionStorage.getSession(tabID) + session.updateAcceptCodeMessageId(undefined) + } + + private sendAcceptCodeTelemetry(session: Session, amazonqNumberOfFilesAccepted: number) { + // accepted code telemetry is only to be sent once per iteration of code generation + if (amazonqNumberOfFilesAccepted > 0 && !session.acceptCodeTelemetrySent) { + session.updateAcceptCodeTelemetrySent(true) + telemetry.amazonq_isAcceptedCodeChanges.emit({ + credentialStartUrl: AuthUtil.instance.startUrl, + amazonqConversationId: session.conversationId, + amazonqNumberOfFilesAccepted, + enabled: true, + result: 'Succeeded', + }) + } + } } diff --git a/packages/core/src/amazonqFeatureDev/errors.ts b/packages/core/src/amazonqFeatureDev/errors.ts index 61804b4a79c..06e994080f3 100644 --- a/packages/core/src/amazonqFeatureDev/errors.ts +++ b/packages/core/src/amazonqFeatureDev/errors.ts @@ -17,8 +17,6 @@ export class ConversationIdNotFoundError extends ToolkitError { } export class TabIdNotFoundError extends ToolkitError { - static errorName = 'TabIdNotFoundError' - constructor() { super(i18n('AWS.amazonq.featureDev.error.tabIdNotFoundError'), { code: 'TabIdNotFound', @@ -26,12 +24,6 @@ export class TabIdNotFoundError extends ToolkitError { } } -export class PanelLoadError extends ToolkitError { - constructor() { - super(`${featureName} UI panel failed to load`, { code: 'PanelLoadFailed' }) - } -} - export class WorkspaceFolderNotFoundError extends ToolkitError { constructor() { super(i18n('AWS.amazonq.featureDev.error.workspaceFolderNotFoundError'), { @@ -41,7 +33,6 @@ export class WorkspaceFolderNotFoundError extends ToolkitError { } export class UserMessageNotFoundError extends ToolkitError { - static errorName = 'UserMessageNotFoundError' constructor() { super(i18n('AWS.amazonq.featureDev.error.userMessageNotFoundError'), { code: 'MessageNotFound', @@ -58,7 +49,6 @@ export class SelectedFolderNotInWorkspaceFolderError extends ToolkitError { } export class PromptRefusalException extends ToolkitError { - static errorName = 'PromptRefusalException' constructor() { super(i18n('AWS.amazonq.featureDev.error.promptRefusalException'), { code: 'PromptRefusalException', @@ -66,15 +56,21 @@ export class PromptRefusalException extends ToolkitError { } } +export class NoChangeRequiredException extends ToolkitError { + constructor() { + super(i18n('AWS.amazonq.featureDev.error.noChangeRequiredException'), { + code: 'NoChangeRequiredException', + }) + } +} + export class FeatureDevServiceError extends ToolkitError { - static errorName = 'FeatureDevServiceError' constructor(message: string, code: string) { super(message, { code }) } } export class PrepareRepoFailedError extends ToolkitError { - static errorName = 'PrepareRepoFailedError' constructor() { super(i18n('AWS.amazonq.featureDev.error.prepareRepoFailedError'), { code: 'PrepareRepoFailed', @@ -83,12 +79,17 @@ export class PrepareRepoFailedError extends ToolkitError { } export class UploadCodeError extends ToolkitError { - static errorName = 'UploadCodeError' constructor(statusCode: string) { super(uploadCodeError, { code: `UploadCode-${statusCode}` }) } } +export class UploadURLExpired extends ToolkitError { + constructor() { + super(i18n('AWS.amazonq.featureDev.error.uploadURLExpired'), { code: 'UploadURLExpired' }) + } +} + export class IllegalStateTransition extends ToolkitError { constructor() { super(i18n('AWS.amazonq.featureDev.error.illegalStateTransition'), { code: 'IllegalStateTransition' }) @@ -96,30 +97,26 @@ export class IllegalStateTransition extends ToolkitError { } export class ContentLengthError extends ToolkitError { - static errorName = 'ContentLengthError' constructor() { - super(i18n('AWS.amazonq.featureDev.error.contentLengthError'), { code: ContentLengthError.errorName }) + super(i18n('AWS.amazonq.featureDev.error.contentLengthError'), { code: ContentLengthError.name }) } } export class ZipFileError extends ToolkitError { - static errorName = 'ZipFileError' constructor() { - super(i18n('AWS.amazonq.featureDev.error.zipFileError'), { code: ZipFileError.errorName }) + super(i18n('AWS.amazonq.featureDev.error.zipFileError'), { code: ZipFileError.name }) } } export class CodeIterationLimitError extends ToolkitError { - static errorName = 'CodeIterationLimitError' constructor() { - super(i18n('AWS.amazonq.featureDev.error.codeIterationLimitError'), { code: CodeIterationLimitError.errorName }) + super(i18n('AWS.amazonq.featureDev.error.codeIterationLimitError'), { code: CodeIterationLimitError.name }) } } export class MonthlyConversationLimitError extends ToolkitError { - static errorName = 'MonthlyConversationLimitError' constructor(message: string) { - super(message, { code: MonthlyConversationLimitError.errorName }) + super(message, { code: MonthlyConversationLimitError.name }) } } diff --git a/packages/core/src/amazonqFeatureDev/index.ts b/packages/core/src/amazonqFeatureDev/index.ts index d215ac3959a..22815f67143 100644 --- a/packages/core/src/amazonqFeatureDev/index.ts +++ b/packages/core/src/amazonqFeatureDev/index.ts @@ -10,10 +10,7 @@ export * from './session/sessionState' export * from './constants' export { Session } from './session/session' export { FeatureDevClient } from './client/featureDev' -export { Messenger } from './controllers/chat/messenger/messenger' -export { ChatSessionStorage } from './storages/chatSession' -export { AppToWebViewMessageDispatcher } from './views/connector/connector' +export { FeatureDevChatSessionStorage } from './storages/chatSession' export { TelemetryHelper } from './util/telemetryHelper' export { prepareRepoData } from './util/files' export { ChatControllerEventEmitters, FeatureDevController } from './controllers/chat/controller' -export { createSessionConfig } from './session/sessionConfigFactory' diff --git a/packages/core/src/amazonqFeatureDev/session/session.ts b/packages/core/src/amazonqFeatureDev/session/session.ts index 01e6c9a2e45..0f6d13bc0e9 100644 --- a/packages/core/src/amazonqFeatureDev/session/session.ts +++ b/packages/core/src/amazonqFeatureDev/session/session.ts @@ -12,21 +12,27 @@ import { type NewFileInfo, type SessionState, type SessionStateConfig, + UpdateFilesPathsParams, } from '../types' import { ConversationIdNotFoundError } from '../errors' -import { referenceLogText } from '../constants' +import { featureDevChat, referenceLogText, featureDevScheme } from '../constants' import fs from '../../shared/fs/fs' -import { Messenger } from '../controllers/chat/messenger/messenger' import { FeatureDevClient } from '../client/featureDev' import { codeGenRetryLimit } from '../limits' -import { SessionConfig } from './sessionConfigFactory' import { telemetry } from '../../shared/telemetry/telemetry' import { TelemetryHelper } from '../util/telemetryHelper' import { ReferenceLogViewProvider } from '../../codewhisperer/service/referenceLogViewProvider' import { AuthUtil } from '../../codewhisperer/util/authUtil' import { getLogger } from '../../shared' import { logWithConversationId } from '../userFacingText' - +import { CodeReference } from '../../amazonq/webview/ui/connector' +import { MynahIcons } from '@aws/mynah-ui' +import { i18n } from '../../shared/i18n-helper' +import { computeDiff } from '../../amazonq/commons/diff' +import { UpdateAnswerMessage } from '../../amazonq/commons/connector/connectorMessages' +import { FollowUpTypes } from '../../amazonq/commons/types' +import { SessionConfig } from '../../amazonq/commons/session/sessionConfigFactory' +import { Messenger } from '../../amazonq/commons/connector/baseMessenger' export class Session { private _state?: SessionState | Omit private task: string = '' @@ -36,6 +42,10 @@ export class Session { private preloaderFinished = false private _latestMessage: string = '' private _telemetry: TelemetryHelper + private _codeResultMessageId: string | undefined = undefined + private _acceptCodeMessageId: string | undefined = undefined + private _acceptCodeTelemetrySent = false + private _reportedCodeChanges: Set // Used to keep track of whether or not the current session is currently authenticating/needs authenticating public isAuthenticating: boolean @@ -54,6 +64,7 @@ export class Session { this._telemetry = new TelemetryHelper() this.isAuthenticating = false + this._reportedCodeChanges = new Set() } /** @@ -89,6 +100,7 @@ export class Session { ...this.getSessionStateConfig(), conversationId: this.conversationId, uploadId: '', + currentCodeGenerationId: undefined, }, [], [], @@ -130,12 +142,14 @@ export class Session { fs: this.config.fs, messenger: this.messenger, telemetry: this.telemetry, + tokenSource: this.state.tokenSource, + uploadHistory: this.state.uploadHistory, }) if (resp.nextState) { - // Cancel the request before moving to a new state - this.state.tokenSource.cancel() - + if (!this.state?.tokenSource?.token.isCancellationRequested) { + this.state?.tokenSource?.cancel() + } // Move to the next state this._state = resp.nextState } @@ -143,17 +157,64 @@ export class Session { return resp.interaction } - public async updateFilesPaths( - tabID: string, - filePaths: NewFileInfo[], - deletedFiles: DeletedFileInfo[], - messageId: string - ) { - this.messenger.updateFileComponent(tabID, filePaths, deletedFiles, messageId) + public async updateFilesPaths(params: UpdateFilesPathsParams) { + const { tabID, filePaths, deletedFiles, messageId, disableFileActions = false } = params + this.messenger.updateFileComponent(tabID, filePaths, deletedFiles, messageId, disableFileActions) + await this.updateChatAnswer(tabID, this.getInsertCodePillText([...filePaths, ...deletedFiles])) + } + + public async updateChatAnswer(tabID: string, insertCodePillText: string) { + if (this._acceptCodeMessageId) { + const answer = new UpdateAnswerMessage( + { + messageId: this._acceptCodeMessageId, + messageType: 'system-prompt', + followUps: [ + { + pillText: insertCodePillText, + type: FollowUpTypes.InsertCode, + icon: 'ok' as MynahIcons, + status: 'success', + }, + { + pillText: i18n('AWS.amazonq.featureDev.pillText.provideFeedback'), + type: FollowUpTypes.ProvideFeedbackAndRegenerateCode, + icon: 'refresh' as MynahIcons, + status: 'info', + }, + ], + }, + tabID, + featureDevChat + ) + this.messenger.updateChatAnswer(answer) + } } public async insertChanges() { - for (const filePath of this.state.filePaths?.filter((i) => !i.rejected) ?? []) { + const newFilePaths = + this.state.filePaths?.filter((filePath) => !filePath.rejected && !filePath.changeApplied) ?? [] + await this.insertNewFiles(newFilePaths) + + const deletedFiles = + this.state.deletedFiles?.filter((deletedFile) => !deletedFile.rejected && !deletedFile.changeApplied) ?? [] + await this.applyDeleteFiles(deletedFiles) + + await this.insertCodeReferenceLogs(this.state.references ?? []) + + if (this._codeResultMessageId) { + await this.updateFilesPaths({ + tabID: this.state.tabID, + filePaths: this.state.filePaths ?? [], + deletedFiles: this.state.deletedFiles ?? [], + messageId: this._codeResultMessageId, + }) + } + } + + public async insertNewFiles(newFilePaths: NewFileInfo[]) { + await this.sendLinesOfCodeAcceptedTelemetry(newFilePaths) + for (const filePath of newFilePaths) { const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath) const uri = filePath.virtualMemoryUri @@ -162,18 +223,124 @@ export class Session { await fs.mkdir(path.dirname(absolutePath)) await fs.writeFile(absolutePath, decodedContent) + filePath.changeApplied = true } + } - for (const filePath of this.state.deletedFiles?.filter((i) => !i.rejected) ?? []) { + public async applyDeleteFiles(deletedFiles: DeletedFileInfo[]) { + for (const filePath of deletedFiles) { const absolutePath = path.join(filePath.workspaceFolder.uri.fsPath, filePath.relativePath) await fs.delete(absolutePath) + filePath.changeApplied = true } + } - for (const ref of this.state.references ?? []) { + public async insertCodeReferenceLogs(codeReferences: CodeReference[]) { + for (const ref of codeReferences) { ReferenceLogViewProvider.instance.addReferenceLog(referenceLogText(ref)) } } + public async disableFileList() { + if (this._codeResultMessageId === undefined) { + return + } + + await this.updateFilesPaths({ + tabID: this.state.tabID, + filePaths: this.state.filePaths ?? [], + deletedFiles: this.state.deletedFiles ?? [], + messageId: this._codeResultMessageId, + disableFileActions: true, + }) + this._codeResultMessageId = undefined + } + + public updateCodeResultMessageId(messageId?: string) { + this._codeResultMessageId = messageId + } + + public updateAcceptCodeMessageId(messageId?: string) { + this._acceptCodeMessageId = messageId + } + + public updateAcceptCodeTelemetrySent(sent: boolean) { + this._acceptCodeTelemetrySent = sent + } + + public getInsertCodePillText(files: Array) { + if (files.every((file) => file.rejected || file.changeApplied)) { + return i18n('AWS.amazonq.featureDev.pillText.continue') + } + if (files.some((file) => file.rejected || file.changeApplied)) { + return i18n('AWS.amazonq.featureDev.pillText.acceptRemainingChanges') + } + return i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges') + } + + public async computeFilePathDiff(filePath: NewFileInfo) { + const leftPath = `${filePath.workspaceFolder.uri.fsPath}/${filePath.relativePath}` + const rightPath = filePath.virtualMemoryUri.path + const diff = await computeDiff(leftPath, rightPath, this.tabID, featureDevScheme) + return { leftPath, rightPath, ...diff } + } + + public async sendMetricDataTelemetry(operationName: string, result: string) { + await this.proxyClient.sendMetricData({ + metricName: 'Operation', + metricValue: 1, + timestamp: new Date(), + product: 'FeatureDev', + dimensions: [ + { + name: 'operationName', + value: operationName, + }, + { + name: 'result', + value: result, + }, + ], + }) + } + + public async sendLinesOfCodeGeneratedTelemetry() { + let charactersOfCodeGenerated = 0 + let linesOfCodeGenerated = 0 + // deleteFiles are currently not counted because the number of lines added is always 0 + const filePaths = this.state.filePaths ?? [] + for (const filePath of filePaths) { + const { leftPath, changes, charsAdded, linesAdded } = await this.computeFilePathDiff(filePath) + const codeChangeKey = `${leftPath}#@${JSON.stringify(changes)}` + if (this._reportedCodeChanges.has(codeChangeKey)) { + continue + } + charactersOfCodeGenerated += charsAdded + linesOfCodeGenerated += linesAdded + this._reportedCodeChanges.add(codeChangeKey) + } + await this.proxyClient.sendFeatureDevCodeGenerationEvent({ + conversationId: this.conversationId, + charactersOfCodeGenerated, + linesOfCodeGenerated, + }) + } + + public async sendLinesOfCodeAcceptedTelemetry(filePaths: NewFileInfo[]) { + let charactersOfCodeAccepted = 0 + let linesOfCodeAccepted = 0 + for (const filePath of filePaths) { + const { charsAdded, linesAdded } = await this.computeFilePathDiff(filePath) + charactersOfCodeAccepted += charsAdded + linesOfCodeAccepted += linesAdded + } + await this.proxyClient.sendFeatureDevCodeAcceptanceEvent({ + conversationId: this.conversationId, + charactersOfCodeAccepted, + linesOfCodeAccepted, + }) + } + get state() { if (!this._state) { throw new Error("State should be initialized before it's read") @@ -181,6 +348,10 @@ export class Session { return this._state } + get currentCodeGenerationId() { + return this.state.currentCodeGenerationId + } + get uploadId() { if (!('uploadId' in this.state)) { throw new Error("UploadId has to be initialized before it's read") @@ -214,4 +385,12 @@ export class Session { get telemetry() { return this._telemetry } + + get acceptCodeMessageId() { + return this._acceptCodeMessageId + } + + get acceptCodeTelemetrySent() { + return this._acceptCodeTelemetrySent + } } diff --git a/packages/core/src/amazonqFeatureDev/session/sessionState.ts b/packages/core/src/amazonqFeatureDev/session/sessionState.ts index cd673fd5f6c..705232b0536 100644 --- a/packages/core/src/amazonqFeatureDev/session/sessionState.ts +++ b/packages/core/src/amazonqFeatureDev/session/sessionState.ts @@ -13,13 +13,18 @@ import { telemetry } from '../../shared/telemetry/telemetry' import { VirtualFileSystem } from '../../shared/virtualFilesystem' import { VirtualMemoryFile } from '../../shared/virtualMemoryFile' import { featureDevScheme } from '../constants' -import { FeatureDevServiceError, IllegalStateTransition, PromptRefusalException } from '../errors' +import { + FeatureDevServiceError, + IllegalStateTransition, + NoChangeRequiredException, + PromptRefusalException, +} from '../errors' import { CodeGenerationStatus, CurrentWsFolders, DeletedFileInfo, DevPhase, - FollowUpTypes, + Intent, NewFileInfo, NewFileZipContents, SessionState, @@ -31,13 +36,16 @@ import { import { prepareRepoData } from '../util/files' import { TelemetryHelper } from '../util/telemetryHelper' import { uploadCode } from '../util/upload' -import { CodeReference } from '../../amazonq/webview/ui/connector' +import { CodeReference, UploadHistory } from '../../amazonq/webview/ui/connector' import { isPresent } from '../../shared/utilities/collectionUtils' import { AuthUtil } from '../../codewhisperer/util/authUtil' import { randomUUID } from '../../shared/crypto' import { collectFiles, getWorkspaceFoldersByPrefixes } from '../../shared/utilities/workspaceUtils' import { i18n } from '../../shared/i18n-helper' -import { Messenger } from '../controllers/chat/messenger/messenger' +import { Messenger } from '../../amazonq/commons/connector/baseMessenger' +import { FollowUpTypes } from '../../amazonq/commons/types' + +export const EmptyCodeGenID = 'EMPTY_CURRENT_CODE_GENERATION_ID' export class ConversationNotStartedState implements Omit { public tokenSource: vscode.CancellationTokenSource @@ -52,11 +60,13 @@ export class ConversationNotStartedState implements Omit val.index === 0)?.name ?? '' - ] + ]) if (folder === undefined) { + telemetry.toolkit_trackScenario.emit({ + count: 1, + amazonqConversationId: conversationId, + credentialStartUrl: AuthUtil.instance.startUrl, + scenario: 'wsOrphanedDocuments', + }) getLogger().error(`No workspace folder found for file: ${zipFilePath} and prefix: ${prefix}`) continue } @@ -88,13 +104,14 @@ function registerNewFiles( workspaceFolderPrefixes === undefined ? 0 : prefix.length > 0 ? prefix.length + 1 : 0 ), rejected: false, + changeApplied: false, }) } return result } -function getDeletedFileInfos(deletedFiles: string[], workspaceFolders: CurrentWsFolders): DeletedFileInfo[] { +export function getDeletedFileInfos(deletedFiles: string[], workspaceFolders: CurrentWsFolders): DeletedFileInfo[] { const workspaceFolderPrefixes = getWorkspaceFoldersByPrefixes(workspaceFolders) return deletedFiles .map((deletedFilePath) => { @@ -113,18 +130,21 @@ function getDeletedFileInfos(deletedFiles: string[], workspaceFolders: CurrentWs workspaceFolder: folder, relativePath: deletedFilePath.substring(prefixLength), rejected: false, + changeApplied: false, } }) .filter(isPresent) } abstract class CodeGenBase { - private pollCount = 180 - private requestDelay = 10000 - readonly tokenSource: vscode.CancellationTokenSource + private pollCount = 360 + private requestDelay = 5000 + public tokenSource: vscode.CancellationTokenSource public phase: SessionStatePhase = DevPhase.CODEGEN public readonly conversationId: string public readonly uploadId: string + public currentCodeGenerationId?: string + public isCancellationRequested?: boolean constructor( protected config: SessionStateConfig, @@ -133,6 +153,7 @@ abstract class CodeGenBase { this.tokenSource = new vscode.CancellationTokenSource() this.conversationId = config.conversationId this.uploadId = config.uploadId + this.currentCodeGenerationId = config.currentCodeGenerationId || EmptyCodeGenID } async generateCode({ @@ -156,7 +177,7 @@ abstract class CodeGenBase { }> { for ( let pollingIteration = 0; - pollingIteration < this.pollCount && !this.tokenSource.token.isCancellationRequested; + pollingIteration < this.pollCount && !this.isCancellationRequested; ++pollingIteration ) { const codegenResult = await this.config.proxyClient.getCodeGeneration(this.conversationId, codeGenerationId) @@ -169,7 +190,14 @@ abstract class CodeGenBase { case CodeGenerationStatus.COMPLETE: { const { newFileContents, deletedFiles, references } = await this.config.proxyClient.exportResultArchive(this.conversationId) - const newFileInfo = registerNewFiles(fs, newFileContents, this.uploadId, workspaceFolders) + const newFileInfo = registerNewFiles( + fs, + newFileContents, + this.uploadId, + workspaceFolders, + this.conversationId, + featureDevScheme + ) telemetry.setNumberOfFilesGenerated(newFileInfo.length) return { @@ -208,6 +236,9 @@ abstract class CodeGenBase { throw new PromptRefusalException() } case codegenResult.codeGenerationStatusDetail?.includes('EmptyPatch'): { + if (codegenResult.codeGenerationStatusDetail?.includes('NO_CHANGE_REQUIRED')) { + throw new NoChangeRequiredException() + } throw new FeatureDevServiceError( i18n('AWS.amazonq.featureDev.error.codeGen.default'), 'EmptyPatchException' @@ -232,7 +263,7 @@ abstract class CodeGenBase { } } } - if (!this.tokenSource.token.isCancellationRequested) { + if (!this.isCancellationRequested) { // still in progress const errorMessage = i18n('AWS.amazonq.featureDev.error.codeGen.timeout') throw new ToolkitError(errorMessage, { code: 'CodeGenTimeout' }) @@ -252,7 +283,8 @@ export class CodeGenState extends CodeGenBase implements SessionState { public deletedFiles: DeletedFileInfo[], public references: CodeReference[], tabID: string, - private currentIteration: number, + public currentIteration: number, + public uploadHistory: UploadHistory, public codeGenerationRemainingIterationCount?: number, public codeGenerationTotalIterationCount?: number ) { @@ -262,6 +294,12 @@ export class CodeGenState extends CodeGenBase implements SessionState { async interact(action: SessionStateAction): Promise { return telemetry.amazonq_codeGenerationInvoke.run(async (span) => { try { + action.tokenSource?.token.onCancellationRequested(() => { + this.isCancellationRequested = true + if (action.tokenSource) { + this.tokenSource = action.tokenSource + } + }) span.record({ amazonqConversationId: this.config.conversationId, credentialStartUrl: AuthUtil.instance.startUrl, @@ -269,22 +307,27 @@ export class CodeGenState extends CodeGenBase implements SessionState { action.telemetry.setGenerateCodeIteration(this.currentIteration) action.telemetry.setGenerateCodeLastInvocationTime() - - const { codeGenerationId } = await this.config.proxyClient.startCodeGeneration( + const codeGenerationId = randomUUID() + await this.config.proxyClient.startCodeGeneration( this.config.conversationId, this.config.uploadId, - action.msg + action.msg, + Intent.DEV, + codeGenerationId, + this.currentCodeGenerationId ) - action.messenger.sendAnswer({ - message: i18n('AWS.amazonq.featureDev.pillText.generatingCode'), - type: 'answer-part', - tabID: this.tabID, - }) - action.messenger.sendUpdatePlaceholder( - this.tabID, - i18n('AWS.amazonq.featureDev.pillText.generatingCode') - ) + if (!this.isCancellationRequested) { + action.messenger.sendAnswer({ + message: i18n('AWS.amazonq.featureDev.pillText.generatingCode'), + type: 'answer-part', + tabID: this.tabID, + }) + action.messenger.sendUpdatePlaceholder( + this.tabID, + i18n('AWS.amazonq.featureDev.pillText.generatingCode') + ) + } const codeGeneration = await this.generateCode({ messenger: action.messenger, @@ -294,12 +337,27 @@ export class CodeGenState extends CodeGenBase implements SessionState { workspaceFolders: this.config.workspaceFolders, }) + if (codeGeneration && !action.tokenSource?.token.isCancellationRequested) { + this.config.currentCodeGenerationId = codeGenerationId + this.currentCodeGenerationId = codeGenerationId + } + this.filePaths = codeGeneration.newFiles this.deletedFiles = codeGeneration.deletedFiles this.references = codeGeneration.references this.codeGenerationRemainingIterationCount = codeGeneration.codeGenerationRemainingIterationCount this.codeGenerationTotalIterationCount = codeGeneration.codeGenerationTotalIterationCount + if (action.uploadHistory && !action.uploadHistory[codeGenerationId] && codeGenerationId) { + action.uploadHistory[codeGenerationId] = { + timestamp: Date.now(), + uploadId: this.config.uploadId, + filePaths: codeGeneration.newFiles, + deletedFiles: codeGeneration.deletedFiles, + tabId: this.tabID, + } + } + action.telemetry.setAmazonqNumberOfReferences(this.references.length) action.telemetry.recordUserCodeGenerationTelemetry(span, this.conversationId) const nextState = new PrepareCodeGenState( @@ -310,7 +368,11 @@ export class CodeGenState extends CodeGenBase implements SessionState { this.tabID, this.currentIteration + 1, this.codeGenerationRemainingIterationCount, - this.codeGenerationTotalIterationCount + this.codeGenerationTotalIterationCount, + action.uploadHistory, + this.tokenSource, + this.currentCodeGenerationId, + codeGenerationId ) return { nextState, @@ -330,6 +392,7 @@ export class MockCodeGenState implements SessionState { public filePaths: NewFileInfo[] public deletedFiles: DeletedFileInfo[] public readonly conversationId: string + public readonly codeGenerationId?: string public readonly uploadId: string constructor( @@ -356,13 +419,21 @@ export class MockCodeGenState implements SessionState { zipFilePath: f.zipFilePath, fileContent: f.fileContent, })) - this.filePaths = registerNewFiles(action.fs, newFileContents, this.uploadId, this.config.workspaceFolders) + this.filePaths = registerNewFiles( + action.fs, + newFileContents, + this.uploadId, + this.config.workspaceFolders, + this.conversationId, + featureDevScheme + ) this.deletedFiles = [ { zipFilePath: 'src/this-file-should-be-deleted.ts', workspaceFolder: this.config.workspaceFolders[0], relativePath: 'src/this-file-should-be-deleted.ts', rejected: false, + changeApplied: false, }, ] action.messenger.sendCodeResult( @@ -376,14 +447,15 @@ export class MockCodeGenState implements SessionState { }, ], this.tabID, - this.uploadId + this.uploadId, + this.codeGenerationId ?? '' ) action.messenger.sendAnswer({ message: undefined, type: 'system-prompt', followUps: [ { - pillText: i18n('AWS.amazonq.featureDev.pillText.insertCode'), + pillText: i18n('AWS.amazonq.featureDev.pillText.acceptAllChanges'), type: FollowUpTypes.InsertCode, icon: 'ok' as MynahIcons, status: 'success', @@ -411,23 +483,30 @@ export class MockCodeGenState implements SessionState { } export class PrepareCodeGenState implements SessionState { - public tokenSource: vscode.CancellationTokenSource public readonly phase = DevPhase.CODEGEN public uploadId: string public conversationId: string + public tokenSource: vscode.CancellationTokenSource constructor( private config: SessionStateConfig, public filePaths: NewFileInfo[], public deletedFiles: DeletedFileInfo[], public references: CodeReference[], public tabID: string, - private currentIteration: number, + public currentIteration: number, public codeGenerationRemainingIterationCount?: number, - public codeGenerationTotalIterationCount?: number + public codeGenerationTotalIterationCount?: number, + public uploadHistory: UploadHistory = {}, + public superTokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource(), + public currentCodeGenerationId?: string, + public codeGenerationId?: string ) { - this.tokenSource = new vscode.CancellationTokenSource() + this.tokenSource = superTokenSource || new vscode.CancellationTokenSource() this.uploadId = config.uploadId + this.currentCodeGenerationId = currentCodeGenerationId this.conversationId = config.conversationId + this.uploadHistory = uploadHistory + this.codeGenerationId = codeGenerationId } updateWorkspaceRoot(workspaceRoot: string) { @@ -442,7 +521,6 @@ export class PrepareCodeGenState implements SessionState { }) action.messenger.sendUpdatePlaceholder(this.tabID, i18n('AWS.amazonq.featureDev.pillText.uploadingCode')) - const uploadId = await telemetry.amazonq_createUpload.run(async (span) => { span.record({ amazonqConversationId: this.config.conversationId, @@ -454,35 +532,40 @@ export class PrepareCodeGenState implements SessionState { action.telemetry, span ) - - const { uploadUrl, uploadId, kmsKeyArn } = await this.config.proxyClient.createUploadUrl( + const uploadId = randomUUID() + const { uploadUrl, kmsKeyArn } = await this.config.proxyClient.createUploadUrl( this.config.conversationId, zipFileChecksum, - zipFileBuffer.length + zipFileBuffer.length, + uploadId ) await uploadCode(uploadUrl, zipFileBuffer, zipFileChecksum, kmsKeyArn) - action.messenger.sendAnswer({ - message: i18n('AWS.amazonq.featureDev.pillText.contextGatheringCompleted'), - type: 'answer-part', - tabID: this.tabID, - }) + if (!action.tokenSource?.token.isCancellationRequested) { + action.messenger.sendAnswer({ + message: i18n('AWS.amazonq.featureDev.pillText.contextGatheringCompleted'), + type: 'answer-part', + tabID: this.tabID, + }) - action.messenger.sendUpdatePlaceholder( - this.tabID, - i18n('AWS.amazonq.featureDev.pillText.contextGatheringCompleted') - ) + action.messenger.sendUpdatePlaceholder( + this.tabID, + i18n('AWS.amazonq.featureDev.pillText.contextGatheringCompleted') + ) + } return uploadId }) this.uploadId = uploadId + const nextState = new CodeGenState( - { ...this.config, uploadId }, + { ...this.config, uploadId: this.uploadId, currentCodeGenerationId: this.currentCodeGenerationId }, this.filePaths, this.deletedFiles, this.references, this.tabID, - this.currentIteration + this.currentIteration, + this.uploadHistory ) return nextState.interact(action) } diff --git a/packages/core/src/amazonqFeatureDev/storages/chatSession.ts b/packages/core/src/amazonqFeatureDev/storages/chatSession.ts index cbfa9f5868f..f45576aa9df 100644 --- a/packages/core/src/amazonqFeatureDev/storages/chatSession.ts +++ b/packages/core/src/amazonqFeatureDev/storages/chatSession.ts @@ -3,47 +3,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -import AsyncLock from 'async-lock' -import { Messenger } from '../controllers/chat/messenger/messenger' +import { BaseChatSessionStorage } from '../../amazonq/commons/baseChatStorage' +import { Messenger } from '../../amazonq/commons/connector/baseMessenger' +import { createSessionConfig } from '../../amazonq/commons/session/sessionConfigFactory' +import { featureDevScheme } from '../constants' import { Session } from '../session/session' -import { createSessionConfig } from '../session/sessionConfigFactory' -export class ChatSessionStorage { - private lock = new AsyncLock() - - private sessions: Map = new Map() - - constructor(private readonly messenger: Messenger) {} +export class FeatureDevChatSessionStorage extends BaseChatSessionStorage { + constructor(protected readonly messenger: Messenger) { + super() + } - private async createSession(tabID: string): Promise { - const sessionConfig = await createSessionConfig() + override async createSession(tabID: string): Promise { + const sessionConfig = await createSessionConfig(featureDevScheme) const session = new Session(sessionConfig, this.messenger, tabID) this.sessions.set(tabID, session) return session } - - public async getSession(tabID: string): Promise { - /** - * The lock here is added in order to mitigate amazon Q's eventing fire & forget design when integrating with mynah-ui that creates a race condition here. - * The race condition happens when handleDevFeatureCommand in src/amazonq/webview/ui/quickActions/handler.ts is firing two events after each other to amazonqFeatureDev controller - * This eventually may make code generation fail as at the moment of that event it may get from the storage a session that has not been properly updated. - */ - return this.lock.acquire(tabID, async () => { - const sessionFromStorage = this.sessions.get(tabID) - if (sessionFromStorage === undefined) { - // If a session doesn't already exist just create it - return this.createSession(tabID) - } - return sessionFromStorage - }) - } - - // Find all sessions that are currently waiting to be authenticated - public getAuthenticatingSessions(): Session[] { - return Array.from(this.sessions.values()).filter((session) => session.isAuthenticating) - } - - public deleteSession(tabID: string) { - this.sessions.delete(tabID) - } } diff --git a/packages/core/src/amazonqFeatureDev/types.ts b/packages/core/src/amazonqFeatureDev/types.ts index fafe26a9e24..0bf0c8550de 100644 --- a/packages/core/src/amazonqFeatureDev/types.ts +++ b/packages/core/src/amazonqFeatureDev/types.ts @@ -6,11 +6,11 @@ import * as vscode from 'vscode' import { VirtualFileSystem } from '../shared/virtualFilesystem' import type { CancellationTokenSource } from 'vscode' -import { Messenger } from './controllers/chat/messenger/messenger' import { FeatureDevClient } from './client/featureDev' import { TelemetryHelper } from './util/telemetryHelper' -import { CodeReference } from '../amazonq/webview/ui/connector' +import { CodeReference, UploadHistory } from '../amazonq/webview/ui/connector' import { DiffTreeFileInfo } from '../amazonq/webview/ui/diffTree/types' +import { Messenger } from '../amazonq/commons/connector/baseMessenger' export type Interaction = { // content to be sent back to the chat UI @@ -21,6 +21,12 @@ export type Interaction = { export interface SessionStateInteraction { nextState: SessionState | Omit | undefined interaction: Interaction + currentCodeGenerationId?: string +} + +export enum Intent { + DEV = 'DEV', + DOC = 'DOC', } export enum DevPhase { @@ -38,18 +44,6 @@ export enum CodeGenerationStatus { FAILED = 'Failed', } -export enum FollowUpTypes { - GenerateCode = 'GenerateCode', - InsertCode = 'InsertCode', - ProvideFeedbackAndRegenerateCode = 'ProvideFeedbackAndRegenerateCode', - Retry = 'Retry', - ModifyDefaultSourceFolder = 'ModifyDefaultSourceFolder', - DevExamples = 'DevExamples', - NewTask = 'NewTask', - CloseSession = 'CloseSession', - SendFeedback = 'SendFeedback', -} - export type SessionStatePhase = DevPhase.INIT | DevPhase.CODEGEN export type CurrentWsFolders = [vscode.WorkspaceFolder, ...vscode.WorkspaceFolder[]] @@ -60,12 +54,16 @@ export interface SessionState { readonly references?: CodeReference[] readonly phase?: SessionStatePhase readonly uploadId: string - readonly tokenSource: CancellationTokenSource + readonly currentIteration?: number + currentCodeGenerationId?: string + tokenSource?: CancellationTokenSource + readonly codeGenerationId?: string readonly tabID: string interact(action: SessionStateAction): Promise updateWorkspaceRoot?: (workspaceRoot: string) => void codeGenerationRemainingIterationCount?: number codeGenerationTotalIterationCount?: number + uploadHistory?: UploadHistory } export interface SessionStateConfig { @@ -74,6 +72,7 @@ export interface SessionStateConfig { conversationId: string proxyClient: FeatureDevClient uploadId: string + currentCodeGenerationId?: string } export interface SessionStateAction { @@ -82,6 +81,8 @@ export interface SessionStateAction { messenger: Messenger fs: VirtualFileSystem telemetry: TelemetryHelper + uploadHistory?: UploadHistory + tokenSource?: CancellationTokenSource } export type NewFileZipContents = { zipFilePath: string; fileContent: string } @@ -106,3 +107,23 @@ export interface SessionStorage { } export type LLMResponseType = 'EMPTY' | 'INVALID_STATE' | 'VALID' + +export interface UpdateFilesPathsParams { + tabID: string + filePaths: NewFileInfo[] + deletedFiles: DeletedFileInfo[] + messageId: string + disableFileActions?: boolean +} + +export enum MetricDataOperationName { + StartCodeGeneration = 'StartCodeGeneration', + EndCodeGeneration = 'EndCodeGeneration', +} + +export enum MetricDataResult { + Success = 'Success', + Fault = 'Fault', + Error = 'Error', + LlmFailure = 'LLMFailure', +} diff --git a/packages/core/src/amazonqFeatureDev/userFacingText.ts b/packages/core/src/amazonqFeatureDev/userFacingText.ts index 781cae997f5..9b8a781ef1a 100644 --- a/packages/core/src/amazonqFeatureDev/userFacingText.ts +++ b/packages/core/src/amazonqFeatureDev/userFacingText.ts @@ -17,7 +17,7 @@ You can use /dev to: To learn more, visit the _[Amazon Q Developer User Guide](${userGuideURL})_. ` -export const uploadCodeError = `I'm sorry, I couldn’t upload your workspace artifacts to Amazon S3 to help you with this task. You might need to allow access to the S3 bucket. For more information, see the [Amazon Q documentation](${manageAccessGuideURL}) or contact your network or organization administrator.` +export const uploadCodeError = `I'm sorry, I couldn't upload your workspace artifacts to Amazon S3 to help you with this task. You might need to allow access to the S3 bucket. For more information, see the [Amazon Q documentation](${manageAccessGuideURL}) or contact your network or organization administrator.` // Utils for logging and showing customer facing conversation id text export const messageWithConversationId = (conversationId?: string) => diff --git a/packages/core/src/amazonqFeatureDev/util/files.ts b/packages/core/src/amazonqFeatureDev/util/files.ts index a5a24139e0f..1b83bdbe2b5 100644 --- a/packages/core/src/amazonqFeatureDev/util/files.ts +++ b/packages/core/src/amazonqFeatureDev/util/files.ts @@ -14,10 +14,11 @@ import { maxFileSizeBytes } from '../limits' import { createHash } from 'crypto' import { CurrentWsFolders } from '../types' import { ToolkitError } from '../../shared/errors' -import { AmazonqCreateUpload, Metric, telemetry as amznTelemetry } from '../../shared/telemetry/telemetry' +import { AmazonqCreateUpload, Span, telemetry as amznTelemetry } from '../../shared/telemetry/telemetry' import { TelemetryHelper } from './telemetryHelper' import { maxRepoSizeBytes } from '../constants' import { isCodeFile } from '../../shared/filetypes' +import { fs } from '../../shared' const getSha256 = (file: Buffer) => createHash('sha256').update(file).digest('base64') @@ -28,17 +29,17 @@ export async function prepareRepoData( repoRootPaths: string[], workspaceFolders: CurrentWsFolders, telemetry: TelemetryHelper, - span: Metric + span: Span, + zip: AdmZip = new AdmZip() ) { try { const files = await collectFiles(repoRootPaths, workspaceFolders, true, maxRepoSizeBytes) - const zip = new AdmZip() let totalBytes = 0 const ignoredExtensionMap = new Map() for (const file of files) { - const fileSize = (await vscode.workspace.fs.stat(file.fileUri)).size + const fileSize = (await fs.stat(file.fileUri)).size const isCodeFile_ = isCodeFile(file.relativeFilePath) if (fileSize >= maxFileSizeBytes || !isCodeFile_) { diff --git a/packages/core/src/amazonqFeatureDev/util/telemetryHelper.ts b/packages/core/src/amazonqFeatureDev/util/telemetryHelper.ts index 53ffd8c7065..111dd19f17e 100644 --- a/packages/core/src/amazonqFeatureDev/util/telemetryHelper.ts +++ b/packages/core/src/amazonqFeatureDev/util/telemetryHelper.ts @@ -5,7 +5,7 @@ import { globals } from '../../shared' import { getLogger } from '../../shared/logger/logger' -import { AmazonqApproachInvoke, AmazonqCodeGenerationInvoke, Metric } from '../../shared/telemetry/telemetry' +import { AmazonqApproachInvoke, AmazonqCodeGenerationInvoke, Span } from '../../shared/telemetry/telemetry' import { LLMResponseType } from '../types' export class TelemetryHelper { @@ -32,7 +32,7 @@ export class TelemetryHelper { } public recordUserApproachTelemetry( - span: Metric, + span: Span, amazonqConversationId: string, responseType: LLMResponseType ) { @@ -48,7 +48,7 @@ export class TelemetryHelper { span.record(event) } - public recordUserCodeGenerationTelemetry(span: Metric, amazonqConversationId: string) { + public recordUserCodeGenerationTelemetry(span: Span, amazonqConversationId: string) { const event = { amazonqConversationId, amazonqGenerateCodeIteration: this.generateCodeIteration, diff --git a/packages/core/src/amazonqFeatureDev/util/upload.ts b/packages/core/src/amazonqFeatureDev/util/upload.ts index 1b7b6a4404b..f9324cfacb8 100644 --- a/packages/core/src/amazonqFeatureDev/util/upload.ts +++ b/packages/core/src/amazonqFeatureDev/util/upload.ts @@ -7,7 +7,9 @@ import request, { RequestError } from '../../shared/request' import { getLogger } from '../../shared/logger/logger' import { featureName } from '../constants' -import { UploadCodeError } from '../errors' +import { UploadCodeError, UploadURLExpired } from '../errors' +import { ToolkitError } from '../../shared' +import { i18n } from '../../shared/i18n-helper' /** * uploadCode @@ -30,8 +32,16 @@ export async function uploadCode(url: string, buffer: Buffer, checksumSha256: st }).response } catch (e: any) { getLogger().error(`${featureName}: failed to upload code to s3: ${(e as Error).message}`) - throw new UploadCodeError( - e instanceof RequestError ? `${e.response.status}: ${e.response.statusText}` : 'Unknown' - ) + if (e instanceof RequestError) { + switch (e.response.status) { + case 403: + throw new UploadURLExpired() + default: + throw new UploadCodeError( + e instanceof RequestError ? `${e.response.status}: ${e.response.statusText}` : 'Unknown' + ) + } + } + throw ToolkitError.chain(e, i18n('AWS.amazonq.featureDev.error.codeGen.default')) } } diff --git a/packages/core/src/amazonqFeatureDev/views/actions/uiMessageListener.ts b/packages/core/src/amazonqFeatureDev/views/actions/uiMessageListener.ts index 790dd1a6e05..5d92fb7188c 100644 --- a/packages/core/src/amazonqFeatureDev/views/actions/uiMessageListener.ts +++ b/packages/core/src/amazonqFeatureDev/views/actions/uiMessageListener.ts @@ -64,6 +64,9 @@ export class UIMessageListener { case 'file-click': this.fileClicked(msg) break + case 'store-code-result-message-id': + this.storeCodeResultMessageId(msg) + break } } @@ -108,6 +111,7 @@ export class UIMessageListener { tabID: msg.tabID, filePath: msg.filePath, deleted: msg.deleted, + messageId: msg.messageId, }) } @@ -155,4 +159,11 @@ export class UIMessageListener { codeReference: msg.codeReference, }) } + + private storeCodeResultMessageId(msg: any) { + this.featureDevControllerEventsEmitters?.storeCodeResultMessageId.fire({ + messageId: msg.messageId, + tabID: msg.tabID, + }) + } } diff --git a/packages/core/src/amazonqGumby/activation.ts b/packages/core/src/amazonqGumby/activation.ts index 7350ce91968..87297bff1c6 100644 --- a/packages/core/src/amazonqGumby/activation.ts +++ b/packages/core/src/amazonqGumby/activation.ts @@ -16,7 +16,6 @@ import { transformByQState } from '../codewhisperer/models/model' import { ProposedTransformationExplorer } from '../codewhisperer/service/transformByQ/transformationResultsViewProvider' import { CodeTransformTelemetryState } from './telemetry/codeTransformTelemetryState' import { telemetry } from '../shared/telemetry/telemetry' -import { CancelActionPositions } from './telemetry/codeTransformTelemetry' import { setContext } from '../shared' export async function activate(context: ExtContext) { @@ -50,9 +49,9 @@ export async function activate(context: ExtContext) { context.extensionContext.subscriptions.push( vscode.window.registerWebviewViewProvider('aws.amazonq.transformationHub', transformationHubViewProvider), - Commands.register('aws.amazonq.stopTransformationInHub', async (cancelSrc: CancelActionPositions) => { + Commands.register('aws.amazonq.stopTransformationInHub', async () => { if (transformByQState.isRunning()) { - await stopTransformByQ(transformByQState.getJobId(), cancelSrc) + await stopTransformByQ(transformByQState.getJobId()) await postTransformationJob() await cleanupTransformationJob() } diff --git a/packages/core/src/amazonqGumby/app.ts b/packages/core/src/amazonqGumby/app.ts index d8cba099509..1638231ef24 100644 --- a/packages/core/src/amazonqGumby/app.ts +++ b/packages/core/src/amazonqGumby/app.ts @@ -71,4 +71,5 @@ export function init(appContext: AmazonQAppInitContext) { showTransformationHub.register() transformByQState.setChatControllers(gumbyChatControllerEventEmitters) + transformByQState.setChatMessenger(messenger) } diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index bc1c93ae5a4..e79ce883e0a 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -5,7 +5,7 @@ * This class is responsible for responding to UI events by calling * the Gumby extension. */ -import nodefs from 'fs' +import nodefs from 'fs' // eslint-disable-line no-restricted-imports import path from 'path' import * as vscode from 'vscode' import { GumbyNamedMessages, Messenger } from './messenger/messenger' @@ -19,16 +19,15 @@ import { cleanupTransformationJob, compileProject, finishHumanInTheLoop, - getValidCandidateProjects, - openBuildLogFile, - openHilPomFile, - parseBuildFile, + getValidLanguageUpgradeCandidateProjects, postTransformationJob, - processTransformFormInput, + processLanguageUpgradeTransformFormInput, + processSQLConversionTransformFormInput, startTransformByQ, stopTransformByQ, validateCanCompileProject, - setMaven, + getValidSQLConversionCandidateProjects, + openHilPomFile, } from '../../../codewhisperer/commands/startTransformByQ' import { JDKVersion, TransformationCandidateProject, transformByQState } from '../../../codewhisperer/models/model' import { @@ -39,6 +38,7 @@ import { ModuleUploadError, NoJavaProjectsFoundError, NoMavenJavaProjectsFoundError, + NoOpenProjectsError, TransformationPreBuildError, } from '../../errors' import * as CodeWhispererConstants from '../../../codewhisperer/models/constants' @@ -52,10 +52,18 @@ import { } from '../../../shared/telemetry/telemetry' import { MetadataResult } from '../../../shared/telemetry/telemetryClient' import { CodeTransformTelemetryState } from '../../telemetry/codeTransformTelemetryState' -import { getAuthType } from '../../../codewhisperer/service/transformByQ/transformApiHandler' import DependencyVersions from '../../models/dependencies' import { getStringHash } from '../../../shared/utilities/textUtilities' import { getVersionData } from '../../../codewhisperer/service/transformByQ/transformMavenHandler' +import AdmZip from 'adm-zip' +import { AuthError } from '../../../auth/sso/server' +import { + setMaven, + openBuildLogFile, + parseBuildFile, + validateSQLMetadataFile, +} from '../../../codewhisperer/service/transformByQ/transformFileHandler' +import { getAuthType } from '../../../auth/utils' // These events can be interactions within the chat, // or elsewhere in the IDE @@ -183,76 +191,110 @@ export class GumbyController { } private async transformInitiated(message: any) { - // Start /transform chat flow - const session: Session = this.sessionStorage.getSession() - CodeTransformTelemetryState.instance.setSessionId() + // silently check for projects eligible for SQL conversion + let embeddedSQLProjects: TransformationCandidateProject[] = [] + try { + embeddedSQLProjects = await getValidSQLConversionCandidateProjects() + } catch (err) { + getLogger().error(`CodeTransformation: error validating SQL conversion projects: ${err}`) + } + if (embeddedSQLProjects.length === 0) { + await this.handleLanguageUpgrade(message) + return + } + + let javaUpgradeProjects: TransformationCandidateProject[] = [] try { - await telemetry.codeTransform_initiateTransform.run(async () => { - const authType = await getAuthType() - telemetry.record({ - codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), - credentialSourceId: authType, - }) + javaUpgradeProjects = await getValidLanguageUpgradeCandidateProjects() + } catch (err) { + getLogger().error(`CodeTransformation: error validating Java upgrade projects: ${err}`) + } - // check that a project is open - const workspaceFolders = vscode.workspace.workspaceFolders - if (workspaceFolders === undefined || workspaceFolders.length === 0) { - this.messenger.sendUnrecoverableErrorResponse('no-project-found', message.tabID) - telemetry.record({ result: MetadataResult.Fail, reason: 'no-project-found' }) - return - } + if (javaUpgradeProjects.length === 0) { + await this.handleSQLConversion(message) + return + } - // check that the session is authenticated - const authState = await AuthUtil.instance.getChatAuthState() - if (authState.amazonQ !== 'connected') { - void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID) - session.isAuthenticating = true - telemetry.record({ result: MetadataResult.Fail, reason: 'auth-failed' }) - return - } + // if previous transformation was already running, show correct message to user + switch (this.sessionStorage.getSession().conversationState) { + case ConversationState.JOB_SUBMITTED: + this.messenger.sendAsyncEventProgress( + message.tabID, + true, + undefined, + GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE + ) + this.messenger.sendJobSubmittedMessage(message.tabID) + return + case ConversationState.COMPILING: + this.messenger.sendAsyncEventProgress( + message.tabID, + true, + undefined, + GumbyNamedMessages.COMPILATION_PROGRESS_MESSAGE + ) + this.messenger.sendCompilationInProgress(message.tabID) + return + } - // If previous transformation was already running - switch (this.sessionStorage.getSession().conversationState) { - case ConversationState.JOB_SUBMITTED: - this.messenger.sendAsyncEventProgress( - message.tabID, - true, - undefined, - GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE - ) - this.messenger.sendJobSubmittedMessage(message.tabID) - return - case ConversationState.COMPILING: - this.messenger.sendAsyncEventProgress( - message.tabID, - true, - undefined, - GumbyNamedMessages.COMPILATION_PROGRESS_MESSAGE - ) - this.messenger.sendCompilationInProgress(message.tabID) - return - } - this.messenger.sendTransformationIntroduction(message.tabID) + // Start /transform chat flow + CodeTransformTelemetryState.instance.setSessionId() + + this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_TRANSFORMATION_OBJECTIVE + this.messenger.sendStaticTextResponse('choose-transformation-objective', message.tabID) + this.messenger.sendChatInputEnabled(message.tabID, true) + this.messenger.sendUpdatePlaceholder( + message.tabID, + CodeWhispererConstants.chooseTransformationObjectivePlaceholder + ) + } + + private async beginTransformation(message: any) { + await telemetry.codeTransform_initiateTransform.run(async () => { + const authType = await getAuthType() + telemetry.record({ + codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), + credentialSourceId: authType, }) - } catch (e: any) { - // if there was an issue getting the list of valid projects, the error message will be shown here - this.messenger.sendErrorMessage(e.message, message.tabID) + + const authState = await AuthUtil.instance.getChatAuthState() + if (authState.amazonQ !== 'connected') { + this.sessionStorage.getSession().isAuthenticating = true + await this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID) + throw new AuthError('Not connected to Amazon Q', `AuthState=${authState.amazonQ}`) + } + this.messenger.sendTransformationIntroduction(message.tabID) + }) + } + + private async handleLanguageUpgrade(message: any) { + try { + await this.beginTransformation(message) + const validProjects = await this.validateLanguageUpgradeProjects(message) + if (validProjects.length > 0) { + this.sessionStorage.getSession().updateCandidateProjects(validProjects) + await this.messenger.sendLanguageUpgradeProjectPrompt(validProjects, message.tabID) + } + } catch (err: any) { + getLogger().error(`Error handling language upgrade: ${err}`) } + } + private async handleSQLConversion(message: any) { try { - const validProjects = await this.validateProjectsWithReplyOnError(message) + await this.beginTransformation(message) + const validProjects = await this.validateSQLConversionProjects(message) if (validProjects.length > 0) { this.sessionStorage.getSession().updateCandidateProjects(validProjects) - await this.messenger.sendProjectPrompt(validProjects, message.tabID) + await this.messenger.sendSelectSQLMetadataFileMessage(message.tabID) } } catch (err: any) { - // if there was an issue getting the list of valid projects, the error message will be shown here - this.messenger.sendErrorMessage(err.message, message.tabID) + getLogger().error(`Error handling SQL conversion: ${err}`) } } - private async validateProjectsWithReplyOnError(message: any): Promise { + private async validateLanguageUpgradeProjects(message: any) { let telemetryJavaVersion = JDKToTelemetryValue(JDKVersion.UNSUPPORTED) as CodeTransformJavaSourceVersionsAllowed try { const validProjects = await telemetry.codeTransform_validateProject.run(async () => { @@ -261,7 +303,7 @@ export class GumbyController { codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), }) - const validProjects = await getValidCandidateProjects() + const validProjects = await getValidLanguageUpgradeCandidateProjects() if (validProjects.length > 0) { // validProjects[0].JDKVersion will be undefined if javap errors out or no .class files found, so call it UNSUPPORTED const javaVersion = validProjects[0].JDKVersion ?? JDKVersion.UNSUPPORTED @@ -282,7 +324,21 @@ export class GumbyController { this.messenger.sendUnrecoverableErrorResponse('no-java-project-found', message.tabID) } else if (e instanceof NoMavenJavaProjectsFoundError) { this.messenger.sendUnrecoverableErrorResponse('no-maven-java-project-found', message.tabID) - } else { + } else if (e instanceof NoOpenProjectsError) { + this.messenger.sendUnrecoverableErrorResponse('no-project-found', message.tabID) + } + } + return [] + } + + private async validateSQLConversionProjects(message: any) { + try { + const validProjects = await getValidSQLConversionCandidateProjects() + return validProjects + } catch (e: any) { + if (e instanceof NoJavaProjectsFoundError) { + this.messenger.sendUnrecoverableErrorResponse('no-java-project-found', message.tabID) + } else if (e instanceof NoOpenProjectsError) { this.messenger.sendUnrecoverableErrorResponse('no-project-found', message.tabID) } } @@ -292,8 +348,8 @@ export class GumbyController { private async formActionClicked(message: any) { const typedAction = MessengerUtils.stringToEnumValue(ButtonActions, message.action as any) switch (typedAction) { - case ButtonActions.CONFIRM_TRANSFORMATION_FORM: - await this.handleUserProjectSelection(message) + case ButtonActions.CONFIRM_LANGUAGE_UPGRADE_TRANSFORMATION_FORM: + await this.handleUserLanguageUpgradeProjectChoice(message) break case ButtonActions.CANCEL_TRANSFORMATION_FORM: telemetry.codeTransform_submitSelection.emit({ @@ -301,7 +357,11 @@ export class GumbyController { userChoice: 'Cancel', result: MetadataResult.Pass, }) - this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage) + this.transformationFinished({ + message: CodeWhispererConstants.jobCancelledChatMessage, + tabID: message.tabID, + includeStartNewTransformationButton: true, + }) break case ButtonActions.CONFIRM_SKIP_TESTS_FORM: await this.handleSkipTestsSelection(message) @@ -309,12 +369,24 @@ export class GumbyController { case ButtonActions.CANCEL_SKIP_TESTS_FORM: this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage) break + case ButtonActions.CONFIRM_SELECTIVE_TRANSFORMATION_FORM: + await this.handleOneOrMultipleDiffs(message) + break + case ButtonActions.CANCEL_SELECTIVE_TRANSFORMATION_FORM: + this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage) + break + case ButtonActions.CONFIRM_SQL_CONVERSION_TRANSFORMATION_FORM: + await this.handleUserSQLConversionProjectSelection(message) + break + case ButtonActions.SELECT_SQL_CONVERSION_METADATA_FILE: + await this.processMetadataFile(message) + break case ButtonActions.VIEW_TRANSFORMATION_HUB: await vscode.commands.executeCommand(GumbyCommands.FOCUS_TRANSFORMATION_HUB, CancelActionPositions.Chat) this.messenger.sendJobSubmittedMessage(message.tabID) break case ButtonActions.STOP_TRANSFORMATION_JOB: - await stopTransformByQ(transformByQState.getJobId(), CancelActionPositions.Chat) + await stopTransformByQ(transformByQState.getJobId()) await postTransformationJob() await cleanupTransformationJob() break @@ -341,63 +413,105 @@ export class GumbyController { } private async handleSkipTestsSelection(message: any) { - const skipTestsSelection = message.formSelectedValues['GumbyTransformSkipTestsForm'] - if (skipTestsSelection === CodeWhispererConstants.skipUnitTestsMessage) { - transformByQState.setCustomBuildCommand(CodeWhispererConstants.skipUnitTestsBuildCommand) - } else { - transformByQState.setCustomBuildCommand(CodeWhispererConstants.doNotSkipUnitTestsBuildCommand) - } - telemetry.codeTransform_submitSelection.emit({ - codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), - userChoice: skipTestsSelection, - result: MetadataResult.Pass, + await telemetry.codeTransform_submitSelection.run(async () => { + const skipTestsSelection = message.formSelectedValues['GumbyTransformSkipTestsForm'] + if (skipTestsSelection === CodeWhispererConstants.skipUnitTestsMessage) { + transformByQState.setCustomBuildCommand(CodeWhispererConstants.skipUnitTestsBuildCommand) + } else { + transformByQState.setCustomBuildCommand(CodeWhispererConstants.doNotSkipUnitTestsBuildCommand) + } + telemetry.record({ + codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), + userChoice: skipTestsSelection, + }) + this.messenger.sendSkipTestsSelectionMessage(skipTestsSelection, message.tabID) + await this.messenger.sendOneOrMultipleDiffsPrompt(message.tabID) }) - this.messenger.sendSkipTestsSelectionMessage(skipTestsSelection, message.tabID) - // perform local build - await this.validateBuildWithPromptOnError(message) } - // prompt user to pick project and specify source JDK version - private async handleUserProjectSelection(message: any) { + private async handleOneOrMultipleDiffs(message: any) { await telemetry.codeTransform_submitSelection.run(async () => { - const pathToProject: string = message.formSelectedValues['GumbyTransformProjectForm'] + const oneOrMultipleDiffsSelection = message.formSelectedValues['GumbyTransformOneOrMultipleDiffsForm'] + if (oneOrMultipleDiffsSelection === CodeWhispererConstants.multipleDiffsMessage) { + transformByQState.setMultipleDiffs(true) + } else { + transformByQState.setMultipleDiffs(false) + } + + telemetry.record({ + codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), + userChoice: oneOrMultipleDiffsSelection, + }) + + this.messenger.sendOneOrMultipleDiffsMessage(oneOrMultipleDiffsSelection, message.tabID) + // perform local build + await this.validateBuildWithPromptOnError(message) + }) + } + + private async handleUserLanguageUpgradeProjectChoice(message: any) { + await telemetry.codeTransform_submitSelection.run(async () => { + const pathToProject: string = message.formSelectedValues['GumbyTransformLanguageUpgradeProjectForm'] const toJDKVersion: JDKVersion = message.formSelectedValues['GumbyTransformJdkToForm'] const fromJDKVersion: JDKVersion = message.formSelectedValues['GumbyTransformJdkFromForm'] telemetry.record({ + // TODO: remove JavaSource/TargetVersionsAllowed when BI is updated to use source/target codeTransformJavaSourceVersionsAllowed: JDKToTelemetryValue( fromJDKVersion ) as CodeTransformJavaSourceVersionsAllowed, codeTransformJavaTargetVersionsAllowed: JDKToTelemetryValue( toJDKVersion ) as CodeTransformJavaTargetVersionsAllowed, + source: fromJDKVersion, + target: toJDKVersion, codeTransformProjectId: pathToProject === undefined ? telemetryUndefined : getStringHash(pathToProject), - userChoice: 'Confirm', + userChoice: 'Confirm-Java', }) const projectName = path.basename(pathToProject) - this.messenger.sendProjectSelectionMessage(projectName, fromJDKVersion, toJDKVersion, message.tabID) + this.messenger.sendLanguageUpgradeProjectChoiceMessage( + projectName, + fromJDKVersion, + toJDKVersion, + message.tabID + ) - if (fromJDKVersion === JDKVersion.UNSUPPORTED) { - this.messenger.sendUnrecoverableErrorResponse('unsupported-source-jdk-version', message.tabID) - telemetry.record({ - result: MetadataResult.Fail, - reason: 'unsupported-source-jdk-version', - }) - return - } + await processLanguageUpgradeTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion) + await this.messenger.sendSkipTestsPrompt(message.tabID) + }) + } - await processTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion) + private async handleUserSQLConversionProjectSelection(message: any) { + await telemetry.codeTransform_submitSelection.run(async () => { + const pathToProject: string = message.formSelectedValues['GumbyTransformSQLConversionProjectForm'] + const schema: string = message.formSelectedValues['GumbyTransformSQLSchemaForm'] - // TODO: delete this line when backend issue is fixed - await this.validateBuildWithPromptOnError(message) + telemetry.record({ + codeTransformProjectId: pathToProject === undefined ? telemetryUndefined : getStringHash(pathToProject), + source: transformByQState.getSourceDB(), + target: transformByQState.getTargetDB(), + userChoice: 'Confirm-SQL', + }) - // TODO: un-comment this line when backend issue is fixed - // await this.messenger.sendSkipTestsPrompt(message.tabID) + const projectName = path.basename(pathToProject) + this.messenger.sendSQLConversionProjectSelectionMessage(projectName, schema, message.tabID) + + await processSQLConversionTransformFormInput(pathToProject, schema) + + this.messenger.sendAsyncEventProgress( + message.tabID, + true, + undefined, + GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE + ) + this.messenger.sendJobSubmittedMessage(message.tabID) + this.sessionStorage.getSession().conversationState = ConversationState.JOB_SUBMITTED + await startTransformByQ() }) } - private async prepareProjectForSubmission(message: { pathToJavaHome: string; tabID: string }): Promise { + private async prepareLanguageUpgradeProject(message: { pathToJavaHome: string; tabID: string }) { if (message.pathToJavaHome) { transformByQState.setJavaHome(message.pathToJavaHome) getLogger().info( @@ -419,6 +533,7 @@ export class GumbyController { this.messenger.sendCompilationFinished(message.tabID) + // since compilation can potentially take a long time, double check auth const authState = await AuthUtil.instance.getChatAuthState() if (authState.amazonQ !== 'connected') { void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID) @@ -440,6 +555,7 @@ export class GumbyController { await startTransformByQ() } + // only for Language Upgrades private async validateBuildWithPromptOnError(message: any | undefined = undefined): Promise { try { // Check Java Home is set (not yet prebuilding) @@ -450,19 +566,65 @@ export class GumbyController { this.messenger.sendStaticTextResponse('java-home-not-set', message.tabID) this.messenger.sendChatInputEnabled(message.tabID, true) this.messenger.sendUpdatePlaceholder(message.tabID, 'Enter the path to your Java installation.') - return } - throw err + return + } + + await this.prepareLanguageUpgradeProject(message) + } + + private async processMetadataFile(message: any) { + const fileUri = await vscode.window.showOpenDialog({ + canSelectMany: false, + openLabel: 'Select', + filters: { + 'SCT metadata': ['zip'], // Restrict user to only pick a .zip file + }, + }) + + if (!fileUri || fileUri.length === 0) { + // user closed the dialog + this.transformationFinished({ + message: CodeWhispererConstants.jobCancelledChatMessage, + tabID: message.tabID, + includeStartNewTransformationButton: true, + }) + return + } + + const metadataZip = new AdmZip(fileUri[0].fsPath) + const fileEntries = metadataZip.getEntries() + const metadataFile = fileEntries.find((entry) => entry.name.endsWith('.sct')) + if (!metadataFile) { + this.messenger.sendUnrecoverableErrorResponse('invalid-zip-no-sct-file', message.tabID) + return + } + + const fileContents = metadataFile.getData().toString('utf-8') + + const isValidMetadata = await validateSQLMetadataFile(fileContents, message) + if (!isValidMetadata) { + return } - await this.prepareProjectForSubmission(message) + this.messenger.sendSQLConversionMetadataReceivedMessage(message.tabID) + transformByQState.setMetadataPathSQL(fileUri[0].fsPath) + + await this.messenger.sendSQLConversionProjectPrompt( + Array.from(this.sessionStorage.getSession().candidateProjects.values()), + message.tabID + ) } - private transformationFinished(data: { message: string | undefined; tabID: string }) { + private transformationFinished(data: { + message: string | undefined + tabID: string + includeStartNewTransformationButton: boolean + }) { this.resetTransformationChatFlow() // at this point job is either completed, partially_completed, cancelled, or failed if (data.message) { - this.messenger.sendJobFinishedMessage(data.tabID, data.message) + this.messenger.sendJobFinishedMessage(data.tabID, data.message, data.includeStartNewTransformationButton) } } @@ -471,7 +633,7 @@ export class GumbyController { } private startHILIntervention(data: { tabID: string; codeSnippet: string }) { - this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_INPUT + this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_HIL_INPUT this.messenger.sendHumanInTheLoopInitialMessage(data.tabID, data.codeSnippet) } @@ -493,15 +655,36 @@ export class GumbyController { switch (session.conversationState) { case ConversationState.PROMPT_JAVA_HOME: { const pathToJavaHome = extractPath(data.message) - if (pathToJavaHome) { - await this.prepareProjectForSubmission({ + await this.prepareLanguageUpgradeProject({ pathToJavaHome, tabID: data.tabID, }) } else { this.messenger.sendUnrecoverableErrorResponse('invalid-java-home', data.tabID) } + break + } + + case ConversationState.WAITING_FOR_TRANSFORMATION_OBJECTIVE: { + const objective = data.message.trim().toLowerCase() + // since we're prompting the user, their project(s) must be eligible for both types of transformations, so track how often this happens here + if (objective === 'language upgrade' || objective === 'sql conversion') { + telemetry.codeTransform_submitSelection.emit({ + codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), + userChoice: objective, + result: 'Succeeded', + }) + } + if (objective === 'language upgrade') { + await this.handleLanguageUpgrade(data) + } else if (objective === 'sql conversion') { + await this.handleSQLConversion(data) + } else { + // keep prompting user until they enter a valid option + await this.transformInitiated(data) + } + break } } } @@ -547,7 +730,11 @@ export class GumbyController { try { await finishHumanInTheLoop() } catch (err: any) { - this.transformationFinished({ tabID: message.tabID, message: (err as Error).message }) + this.transformationFinished({ + tabID: message.tabID, + message: (err as Error).message, + includeStartNewTransformationButton: true, + }) } this.messenger.sendStaticTextResponse('end-HIL-early', message.tabID) diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts index 46f40e57fb4..3d947b8caff 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts @@ -35,6 +35,9 @@ export type StaticTextResponseType = | 'start-transformation-confirmed' | 'job-transmitted' | 'end-HIL-early' + | 'choose-transformation-objective' + | 'language-upgrade-selected' + | 'sql-conversion-selected' export type UnrecoverableErrorType = | 'no-project-found' @@ -42,9 +45,12 @@ export type UnrecoverableErrorType = | 'no-maven-java-project-found' | 'could-not-compile-project' | 'invalid-java-home' - | 'unsupported-source-jdk-version' | 'upload-to-s3-failed' | 'job-start-failed' + | 'unsupported-source-db' + | 'unsupported-target-db' + | 'error-parsing-sct-file' + | 'invalid-zip-no-sct-file' export enum GumbyNamedMessages { COMPILATION_PROGRESS_MESSAGE = 'gumbyProjectCompilationMessage', @@ -148,7 +154,46 @@ export class Messenger { ) } - public async sendProjectPrompt(projects: TransformationCandidateProject[], tabID: string) { + public async sendOneOrMultipleDiffsPrompt(tabID: string) { + const formItems: ChatItemFormItem[] = [] + formItems.push({ + id: 'GumbyTransformOneOrMultipleDiffsForm', + type: 'select', + title: CodeWhispererConstants.selectiveTransformationFormTitle, + mandatory: true, + options: [ + { + value: CodeWhispererConstants.oneDiffMessage, + label: CodeWhispererConstants.oneDiffMessage, + }, + { + value: CodeWhispererConstants.multipleDiffsMessage, + label: CodeWhispererConstants.multipleDiffsMessage, + }, + ], + }) + + this.dispatcher.sendAsyncEventProgress( + new AsyncEventProgressMessage(tabID, { + inProgress: true, + message: CodeWhispererConstants.userPatchDescriptionChatMessage, + }) + ) + + this.dispatcher.sendChatPrompt( + new ChatPrompt( + { + message: 'Q Code Transformation', + formItems: formItems, + }, + 'TransformOneOrMultipleDiffsForm', + tabID, + false + ) + ) + } + + public async sendLanguageUpgradeProjectPrompt(projects: TransformationCandidateProject[], tabID: string) { const projectFormOptions: { value: any; label: string }[] = [] const detectedJavaVersions = new Array() @@ -162,7 +207,7 @@ export class Messenger { const formItems: ChatItemFormItem[] = [] formItems.push({ - id: 'GumbyTransformProjectForm', + id: 'GumbyTransformLanguageUpgradeProjectForm', type: 'select', title: CodeWhispererConstants.chooseProjectFormTitle, mandatory: true, @@ -178,15 +223,15 @@ export class Messenger { options: [ { value: JDKVersion.JDK8, - label: JDKVersion.JDK8.toString(), + label: JDKVersion.JDK8, }, { value: JDKVersion.JDK11, - label: JDKVersion.JDK11.toString(), + label: JDKVersion.JDK11, }, { - value: JDKVersion.UNSUPPORTED, - label: 'Other', + value: JDKVersion.JDK17, + label: JDKVersion.JDK17, }, ], }) @@ -207,7 +252,64 @@ export class Messenger { this.dispatcher.sendAsyncEventProgress( new AsyncEventProgressMessage(tabID, { inProgress: true, - message: MessengerUtils.createTransformationConfirmationPrompt(detectedJavaVersions), + message: CodeWhispererConstants.projectPromptChatMessage, + }) + ) + + this.dispatcher.sendAsyncEventProgress( + new AsyncEventProgressMessage(tabID, { + inProgress: false, + message: undefined, + }) + ) + + this.dispatcher.sendChatPrompt( + new ChatPrompt( + { + message: 'Q Code Transformation', + formItems: formItems, + }, + 'LanguageUpgradeTransformForm', + tabID, + false + ) + ) + } + + public async sendSQLConversionProjectPrompt(projects: TransformationCandidateProject[], tabID: string) { + const projectFormOptions: { value: any; label: string }[] = [] + + projects.forEach((candidateProject) => { + projectFormOptions.push({ + value: candidateProject.path, + label: candidateProject.name, + }) + }) + + const formItems: ChatItemFormItem[] = [] + formItems.push({ + id: 'GumbyTransformSQLConversionProjectForm', + type: 'select', + title: CodeWhispererConstants.chooseProjectFormTitle, + mandatory: true, + options: projectFormOptions, + }) + + formItems.push({ + id: 'GumbyTransformSQLSchemaForm', + type: 'select', + title: CodeWhispererConstants.chooseSchemaFormTitle, + mandatory: true, + options: Array.from(transformByQState.getSchemaOptions()).map((schema) => ({ + value: schema, + label: schema, + })), + }) + + this.dispatcher.sendAsyncEventProgress( + new AsyncEventProgressMessage(tabID, { + inProgress: true, + message: CodeWhispererConstants.chooseProjectSchemaFormMessage, }) ) @@ -224,7 +326,7 @@ export class Messenger { message: 'Q Code Transformation', formItems: formItems, }, - 'TransformForm', + 'SQLConversionTransformForm', tabID, false ) @@ -299,7 +401,6 @@ export class Messenger { }, tabID ) - this.dispatcher.sendChatMessage(jobSubmittedMessage) } @@ -315,15 +416,18 @@ export class Messenger { ) } - public sendStaticTextResponse(type: StaticTextResponseType, tabID: string) { + public sendStaticTextResponse(messageType: StaticTextResponseType, tabID: string) { let message = '...' - switch (type) { + switch (messageType) { case 'java-home-not-set': message = MessengerUtils.createJavaHomePrompt() break case 'end-HIL-early': - message = `I will continue transforming your code without upgrading this dependency.` + message = 'I will continue transforming your code without upgrading this dependency.' + break + case 'choose-transformation-objective': + message = CodeWhispererConstants.chooseTransformationObjective break } @@ -355,6 +459,7 @@ export class Messenger { message = CodeWhispererConstants.noJavaProjectsFoundChatMessage break case 'no-maven-java-project-found': + // shown when user has no pom.xml, but at this point also means they have no eligible SQL conversion projects message = CodeWhispererConstants.noPomXmlFoundChatMessage break case 'could-not-compile-project': @@ -363,28 +468,21 @@ export class Messenger { case 'invalid-java-home': message = CodeWhispererConstants.noJavaHomeFoundChatMessage break - case 'unsupported-source-jdk-version': - message = CodeWhispererConstants.unsupportedJavaVersionChatMessage + case 'unsupported-source-db': + message = CodeWhispererConstants.invalidMetadataFileUnsupportedSourceDB + break + case 'unsupported-target-db': + message = CodeWhispererConstants.invalidMetadataFileUnsupportedTargetDB + break + case 'error-parsing-sct-file': + message = CodeWhispererConstants.invalidMetadataFileErrorParsing + break + case 'invalid-zip-no-sct-file': + message = CodeWhispererConstants.invalidMetadataFileNoSctFile break } - const buttons: ChatItemButton[] = [] - buttons.push({ - keepCardAfterClick: false, - text: CodeWhispererConstants.startTransformationButtonText, - id: ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW, - }) - - this.dispatcher.sendChatMessage( - new ChatMessage( - { - message, - messageType: 'ai-prompt', - buttons, - }, - tabID - ) - ) + this.sendJobFinishedMessage(tabID, message) } /** @@ -409,13 +507,15 @@ export class Messenger { this.dispatcher.sendCommandMessage(new SendCommandMessage(message.command, message.tabID, message.eventId)) } - public sendJobFinishedMessage(tabID: string, message: string) { + public sendJobFinishedMessage(tabID: string, message: string, includeStartNewTransformationButton: boolean = true) { const buttons: ChatItemButton[] = [] - buttons.push({ - keepCardAfterClick: false, - text: CodeWhispererConstants.startTransformationButtonText, - id: ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW, - }) + if (includeStartNewTransformationButton) { + buttons.push({ + keepCardAfterClick: false, + text: CodeWhispererConstants.startTransformationButtonText, + id: ButtonActions.CONFIRM_START_TRANSFORMATION_FLOW, + }) + } this.dispatcher.sendChatMessage( new ChatMessage( @@ -442,7 +542,7 @@ export class Messenger { ) } - public sendProjectSelectionMessage( + public sendLanguageUpgradeProjectChoiceMessage( projectName: string, fromJDKVersion: JDKVersion, toJDKVersion: JDKVersion, @@ -460,11 +560,45 @@ export class Messenger { this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'prompt' }, tabID)) } + public sendSQLConversionProjectSelectionMessage(projectName: string, schema: string, tabID: string) { + const message = `### Transformation details +------------- +| | | +| :------------------- | -------: | +| **Project** | ${projectName} | +| **Schema** | ${schema} | + ` + this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'prompt' }, tabID)) + } + + public sendSQLConversionMetadataReceivedMessage(tabID: any) { + const message = `### Transformation details +------------- +| | | +| :------------------- | -------: | +| **Source DB** | ${transformByQState.getSourceDB()} | +| **Target DB** | ${transformByQState.getTargetDB()} | +| **Host** | ${transformByQState.getSourceServerName()} | + ` + this.dispatcher.sendChatMessage( + new ChatMessage( + { message: CodeWhispererConstants.sqlMetadataFileReceived, messageType: 'ai-prompt' }, + tabID + ) + ) + this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'ai-prompt' }, tabID)) + } + public sendSkipTestsSelectionMessage(skipTestsSelection: string, tabID: string) { const message = `Okay, I will ${skipTestsSelection.toLowerCase()} when building your project.` this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'ai-prompt' }, tabID)) } + public sendOneOrMultipleDiffsMessage(selectiveTransformationSelection: string, tabID: string) { + const message = `Okay, I will create ${selectiveTransformationSelection.toLowerCase()} with my proposed changes.` + this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'ai-prompt' }, tabID)) + } + public sendHumanInTheLoopInitialMessage(tabID: string, codeSnippet: string) { let message = `I was not able to upgrade all dependencies. To resolve it, I will try to find an updated depedency in your local Maven repository. I will need additional information from you to continue.` @@ -612,4 +746,32 @@ ${codeSnippet} ) ) } + + public async sendSelectSQLMetadataFileMessage(tabID: string) { + const message = CodeWhispererConstants.selectSQLMetadataFileHelpMessage + const buttons: ChatItemButton[] = [] + + buttons.push({ + keepCardAfterClick: true, + text: 'Select metadata file', + id: ButtonActions.SELECT_SQL_CONVERSION_METADATA_FILE, + }) + + buttons.push({ + keepCardAfterClick: false, + text: 'Cancel', + id: ButtonActions.CANCEL_TRANSFORMATION_FORM, + }) + + this.dispatcher.sendChatMessage( + new ChatMessage( + { + message, + messageType: 'ai-prompt', + buttons, + }, + tabID + ) + ) + } } diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts index 184637fbc3b..1eeef162d34 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts @@ -13,10 +13,14 @@ import DependencyVersions from '../../../models/dependencies' export enum ButtonActions { STOP_TRANSFORMATION_JOB = 'gumbyStopTransformationJob', VIEW_TRANSFORMATION_HUB = 'gumbyViewTransformationHub', - CONFIRM_TRANSFORMATION_FORM = 'gumbyTransformFormConfirm', + CONFIRM_LANGUAGE_UPGRADE_TRANSFORMATION_FORM = 'gumbyLanguageUpgradeTransformFormConfirm', + CONFIRM_SQL_CONVERSION_TRANSFORMATION_FORM = 'gumbySQLConversionTransformFormConfirm', + CANCEL_TRANSFORMATION_FORM = 'gumbyTransformFormCancel', // shared between Language Upgrade & SQL Conversion CONFIRM_SKIP_TESTS_FORM = 'gumbyTransformSkipTestsFormConfirm', - CANCEL_TRANSFORMATION_FORM = 'gumbyTransformFormCancel', CANCEL_SKIP_TESTS_FORM = 'gumbyTransformSkipTestsFormCancel', + CONFIRM_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformOneOrMultipleDiffsFormConfirm', + CANCEL_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformOneOrMultipleDiffsFormCancel', + SELECT_SQL_CONVERSION_METADATA_FILE = 'gumbySQLConversionMetadataTransformFormConfirm', CONFIRM_DEPENDENCY_FORM = 'gumbyTransformDependencyFormConfirm', CANCEL_DEPENDENCY_FORM = 'gumbyTransformDependencyFormCancel', CONFIRM_JAVA_HOME_FORM = 'gumbyJavaHomeFormConfirm', @@ -42,9 +46,11 @@ export default class MessengerUtils { } else if (os.platform() === 'darwin') { const jdkVersion = transformByQState.getSourceJDKVersion() if (jdkVersion === JDKVersion.JDK8) { - javaHomePrompt += ` ${CodeWhispererConstants.macJava8HomeHelpChatMessage}` + javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(1.8)}` } else if (jdkVersion === JDKVersion.JDK11) { - javaHomePrompt += ` ${CodeWhispererConstants.macJava11HomeHelpChatMessage}` + javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(11)}` + } else if (jdkVersion === JDKVersion.JDK17) { + javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(17)}` } } else { javaHomePrompt += ` ${CodeWhispererConstants.linuxJavaHomeHelpChatMessage}` @@ -63,30 +69,8 @@ export default class MessengerUtils { } } - static createTransformationConfirmationPrompt = (detectedJavaVersions: Array): string => { - let javaVersionString = 'Java project' - const uniqueJavaOptions = new Set(detectedJavaVersions) - - if (detectedJavaVersions.length > 1) { - // this means there is a Java version whose version we weren't able to determine - if (uniqueJavaOptions.has(undefined)) { - javaVersionString = 'Java projects' - } else { - javaVersionString = `Java ${Array.from(uniqueJavaOptions).join(' & ')} projects` - } - } else if (detectedJavaVersions.length === 1) { - if (!uniqueJavaOptions.has(undefined)) { - javaVersionString = `Java ${detectedJavaVersions[0]!.toString()} project` - } - } - - return CodeWhispererConstants.projectPromptChatMessage.replace('JAVA_VERSION_HERE', javaVersionString) - } - static createAvailableDependencyVersionString = (versions: DependencyVersions): string => { - let message = `I found ${versions.length} other dependency versions that are more recent than the dependency in your code that's causing an error: ${versions.currentVersion}. - -` + let message = `I found ${versions.length} other dependency versions that are more recent than the dependency in your code that's causing an error: ${versions.currentVersion}.` if (versions.majorVersions !== undefined && versions.majorVersions.length > 0) { message = message.concat( diff --git a/packages/core/src/amazonqGumby/chat/session/session.ts b/packages/core/src/amazonqGumby/chat/session/session.ts index f8416fc22a2..b0ed125c7d8 100644 --- a/packages/core/src/amazonqGumby/chat/session/session.ts +++ b/packages/core/src/amazonqGumby/chat/session/session.ts @@ -10,7 +10,8 @@ export enum ConversationState { PROMPT_JAVA_HOME, COMPILING, JOB_SUBMITTED, - WAITING_FOR_INPUT, + WAITING_FOR_HIL_INPUT, + WAITING_FOR_TRANSFORMATION_OBJECTIVE, } export interface ProjectDetails { diff --git a/packages/core/src/amazonqGumby/chat/views/connector/connector.ts b/packages/core/src/amazonqGumby/chat/views/connector/connector.ts index 098682110d6..9e044780858 100644 --- a/packages/core/src/amazonqGumby/chat/views/connector/connector.ts +++ b/packages/core/src/amazonqGumby/chat/views/connector/connector.ts @@ -117,7 +117,8 @@ export class ChatPrompt extends UiMessage { keepCardAfterClick: false, waitMandatoryFormItems: false, text: 'Cancel', - id: `gumby${promptIDPrefix}Cancel`, + // makes the Cancel button shared in all forms, which is fine since we respond the same exact way each time + id: 'gumbyTransformFormCancel', }) } } diff --git a/packages/core/src/amazonqGumby/index.ts b/packages/core/src/amazonqGumby/index.ts index 50005c984bb..8c1109f9997 100644 --- a/packages/core/src/amazonqGumby/index.ts +++ b/packages/core/src/amazonqGumby/index.ts @@ -6,4 +6,8 @@ export { activate } from './activation' export { default as DependencyVersions } from './models/dependencies' export { default as MessengerUtils } from './chat/controller/messenger/messengerUtils' +export { GumbyController } from './chat/controller/controller' +export { TabsStorage } from '../amazonq/webview/ui/storages/tabsStorage' +export * as startTransformByQ from '../../src/codewhisperer/commands/startTransformByQ' +export { setMaven } from '../../src/codewhisperer/service/transformByQ/transformFileHandler' export * from './errors' diff --git a/packages/core/src/amazonqGumby/models/constants.ts b/packages/core/src/amazonqGumby/models/constants.ts index 1478745497a..87f9d456690 100644 --- a/packages/core/src/amazonqGumby/models/constants.ts +++ b/packages/core/src/amazonqGumby/models/constants.ts @@ -7,6 +7,6 @@ export const gumbyChat = 'gumbyChat' // This sets the tab name -export const featureName = 'Q - Code Transform' +export const featureName = 'Q - Code Transformation' export const dependencyNoAvailableVersions = 'no available versions' diff --git a/packages/core/src/amazonqGumby/telemetry/codeTransformTelemetry.ts b/packages/core/src/amazonqGumby/telemetry/codeTransformTelemetry.ts index f2ad37de84d..edcb89bf586 100644 --- a/packages/core/src/amazonqGumby/telemetry/codeTransformTelemetry.ts +++ b/packages/core/src/amazonqGumby/telemetry/codeTransformTelemetry.ts @@ -40,54 +40,4 @@ export const JDKToTelemetryValue = ( } } -/** - * @description We want the output of our Java versions found - * that are not supported to match IntelliJ output. IntelliJ - * can read the version easily and for VSCode we must exec - * the javap -v command. - */ -export const javapOutputToTelemetryValue = (javapCommandLineOutput: string) => { - switch (javapCommandLineOutput) { - case '49': - return 'JDK_1_5' - case '50': - return 'JDK_1_6' - case '51': - return 'JDK_1_7' - case '52': - return 'JDK_1_8' - case '53': - return 'JDK_1_9' - case '54': - return 'JDK_10' - case '55': - return 'JDK_11' - case '56': - return 'JDK_12' - case '57': - return 'JDK_13' - case '58': - return 'JDK_14' - case '59': - return 'JDK_15' - case '60': - return 'JDK_16' - case '61': - return 'JDK_17' - case '62': - return 'JDK_18' - case '63': - return 'JDK_19' - case '64': - return 'JDK_20' - case '65': - return 'JDK_21' - case '66': - return 'JDK_22' - default: - // If nothing found. Output the number and lookup the java 'major version' numbers online - return javapCommandLineOutput - } -} - export const calculateTotalLatency = (startTime: number): number => globals.clock.Date.now() - startTime diff --git a/packages/core/src/amazonqScan/chat/session/session.ts b/packages/core/src/amazonqScan/chat/session/session.ts new file mode 100644 index 00000000000..c50f6291bde --- /dev/null +++ b/packages/core/src/amazonqScan/chat/session/session.ts @@ -0,0 +1,26 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export enum ConversationState { + IDLE, +} + +export class Session { + // Used to keep track of whether or not the current session is currently authenticating/needs authenticating + public isAuthenticating: boolean = false + + // A tab may or may not be currently open + public tabID: string | undefined + + public conversationState: ConversationState = ConversationState.IDLE + + public scanUuid: string | undefined + + constructor() {} + + public isTabOpen(): boolean { + return this.tabID !== undefined + } +} diff --git a/packages/core/src/amazonqScan/chat/storages/chatSession.ts b/packages/core/src/amazonqScan/chat/storages/chatSession.ts new file mode 100644 index 00000000000..b9742a5db95 --- /dev/null +++ b/packages/core/src/amazonqScan/chat/storages/chatSession.ts @@ -0,0 +1,51 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + */ + +import { getLogger } from '../../../shared' +import { Session } from '../session/session' + +export class SessionNotFoundError extends Error {} + +export class ChatSessionManager { + private static _instance: ChatSessionManager + private activeSession: Session | undefined + + constructor() {} + + public static get Instance() { + return this._instance || (this._instance = new this()) + } + + private createSession(): Session { + this.activeSession = new Session() + return this.activeSession + } + + public getSession(): Session { + if (this.activeSession === undefined) { + return this.createSession() + } + + return this.activeSession + } + + public setActiveTab(tabID: string): string { + getLogger().debug(`Setting active tab: ${tabID}, activeSession: ${this.activeSession}`) + if (this.activeSession !== undefined) { + this.activeSession.tabID = tabID + return tabID + } + throw new SessionNotFoundError() + } + + public removeActiveTab(): void { + getLogger().debug(`Removing active tab and deleting activeSession: ${this.activeSession}`) + if (this.activeSession !== undefined) { + this.activeSession.tabID = undefined + this.activeSession = undefined + } + } +} diff --git a/packages/core/src/amazonqScan/connector.ts b/packages/core/src/amazonqScan/connector.ts new file mode 100644 index 00000000000..1b2e05541ec --- /dev/null +++ b/packages/core/src/amazonqScan/connector.ts @@ -0,0 +1,21 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * TODO: + * This file/declaration needs to be moved to packages/amazonq/src/amazonqScan/chat/views/connector/connector + * Once the mapping from Q folder to core is configured. + */ + +export type ScanMessageType = + | 'authenticationUpdateMessage' + | 'authNeededException' + | 'chatMessage' + | 'chatInputEnabledMessage' + | 'sendCommandMessage' + | 'updatePlaceholderMessage' + | 'updatePromptProgress' + | 'chatPrompt' + | 'errorMessage' diff --git a/packages/core/src/amazonqScan/controller.ts b/packages/core/src/amazonqScan/controller.ts new file mode 100644 index 00000000000..7e928d6df69 --- /dev/null +++ b/packages/core/src/amazonqScan/controller.ts @@ -0,0 +1,33 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * This class is responsible for responding to UI events by calling + * the Scan extension. + */ + +/** + * TODO: + * This file/declaration needs to be moved to packages/amazonq/src/amazonqScan/chat/controller + * Once the mapping from Q folder to core is configured. + */ + +import * as vscode from 'vscode' + +// These events can be interactions within the chat, +// or elsewhere in the IDE +export interface ScanChatControllerEventEmitters { + readonly tabOpened: vscode.EventEmitter + readonly tabClosed: vscode.EventEmitter + readonly authClicked: vscode.EventEmitter + readonly runScan: vscode.EventEmitter + readonly formActionClicked: vscode.EventEmitter + readonly errorThrown: vscode.EventEmitter + readonly showSecurityScan: vscode.EventEmitter + readonly scanStopped: vscode.EventEmitter + readonly followUpClicked: vscode.EventEmitter + readonly scanProgress: vscode.EventEmitter + readonly processResponseBodyLinkClick: vscode.EventEmitter + readonly fileClicked: vscode.EventEmitter + readonly scanCancelled: vscode.EventEmitter +} diff --git a/packages/core/src/amazonqScan/index.ts b/packages/core/src/amazonqScan/index.ts new file mode 100644 index 00000000000..7e29450c50c --- /dev/null +++ b/packages/core/src/amazonqScan/index.ts @@ -0,0 +1,9 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export { ScanChatControllerEventEmitters } from './controller' +export { ScanMessageType } from './connector' +export { ChatSessionManager } from './chat/storages/chatSession' +export { Session } from './chat/session/session' diff --git a/packages/core/src/amazonqTest/app.ts b/packages/core/src/amazonqTest/app.ts new file mode 100644 index 00000000000..3f857612520 --- /dev/null +++ b/packages/core/src/amazonqTest/app.ts @@ -0,0 +1,71 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { AmazonQAppInitContext } from '../amazonq/apps/initContext' +import { MessagePublisher } from '../amazonq/messages/messagePublisher' +import { MessageListener } from '../amazonq/messages/messageListener' +import { AuthUtil } from '../codewhisperer/util/authUtil' +import { ChatSessionManager } from './chat/storages/chatSession' +import { TestController, TestChatControllerEventEmitters } from './chat/controller/controller' +import { AppToWebViewMessageDispatcher } from './chat/views/connector/connector' +import { Messenger } from './chat/controller/messenger/messenger' +import { UIMessageListener } from './chat/views/actions/uiMessageListener' +import { debounce } from 'lodash' +import { testGenState } from '../codewhisperer/models/model' + +export function init(appContext: AmazonQAppInitContext) { + const testChatControllerEventEmitters: TestChatControllerEventEmitters = { + tabOpened: new vscode.EventEmitter(), + tabClosed: new vscode.EventEmitter(), + authClicked: new vscode.EventEmitter(), + startTestGen: new vscode.EventEmitter(), + processHumanChatMessage: new vscode.EventEmitter(), + updateShortAnswer: new vscode.EventEmitter(), + showCodeGenerationResults: new vscode.EventEmitter(), + openDiff: new vscode.EventEmitter(), + formActionClicked: new vscode.EventEmitter(), + followUpClicked: new vscode.EventEmitter(), + sendUpdatePromptProgress: new vscode.EventEmitter(), + errorThrown: new vscode.EventEmitter(), + insertCodeAtCursorPosition: new vscode.EventEmitter(), + processResponseBodyLinkClick: new vscode.EventEmitter(), + } + const dispatcher = new AppToWebViewMessageDispatcher(appContext.getAppsToWebViewMessagePublisher()) + const messenger = new Messenger(dispatcher) + + new TestController(testChatControllerEventEmitters, messenger, appContext.onDidChangeAmazonQVisibility.event) + + const testChatUIInputEventEmitter = new vscode.EventEmitter() + + new UIMessageListener({ + chatControllerEventEmitters: testChatControllerEventEmitters, + webViewMessageListener: new MessageListener(testChatUIInputEventEmitter), + }) + + appContext.registerWebViewToAppMessagePublisher(new MessagePublisher(testChatUIInputEventEmitter), 'testgen') + + const debouncedEvent = debounce(async () => { + const authenticated = (await AuthUtil.instance.getChatAuthState()).amazonQ === 'connected' + let authenticatingSessionID = '' + + if (authenticated) { + const session = ChatSessionManager.Instance.getSession() + + if (session.isTabOpen() && session.isAuthenticating) { + authenticatingSessionID = session.tabID! + session.isAuthenticating = false + } + } + + messenger.sendAuthenticationUpdate(authenticated, [authenticatingSessionID]) + }, 500) + + AuthUtil.instance.secondaryAuth.onDidChangeActiveConnection(() => { + return debouncedEvent() + }) + testGenState.setChatControllers(testChatControllerEventEmitters) + // TODO: Add testGen provider for creating new files after test generation if they does not exist +} diff --git a/packages/core/src/amazonqTest/chat/controller/controller.ts b/packages/core/src/amazonqTest/chat/controller/controller.ts new file mode 100644 index 00000000000..79d4d117057 --- /dev/null +++ b/packages/core/src/amazonqTest/chat/controller/controller.ts @@ -0,0 +1,1322 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * This class is responsible for responding to UI events by calling + * the Test extension. + */ +import * as vscode from 'vscode' +import path from 'path' +import { FollowUps, Messenger, TestNamedMessages } from './messenger/messenger' +import { AuthController } from '../../../amazonq/auth/controller' +import { ChatSessionManager } from '../storages/chatSession' +import { BuildStatus, ConversationState, Session } from '../session/session' +import { AuthUtil } from '../../../codewhisperer/util/authUtil' +import { + CodeWhispererConstants, + ReferenceLogViewProvider, + ShortAnswer, + ShortAnswerReference, + TelemetryHelper, + TestGenerationBuildStep, + testGenState, + unitTestGenerationCancelMessage, +} from '../../../codewhisperer' +import { + fs, + getLogger, + getTelemetryReasonDesc, + i18n, + openUrl, + randomUUID, + sleep, + tempDirPath, + testGenerationLogsDir, +} from '../../../shared' +import { + buildProgressField, + cancellingProgressField, + cancelTestGenButton, + errorProgressField, + testGenBuildProgressMessage, + testGenCompletedField, + testGenProgressField, + testGenSummaryMessage, +} from '../../models/constants' +import MessengerUtils, { ButtonActions } from './messenger/messengerUtils' +import { isAwsError } from '../../../shared/errors' +import { ChatItemType } from '../../../amazonq/commons/model' +import { ProgressField } from '@aws/mynah-ui' +import { FollowUpTypes } from '../../../amazonq/commons/types' +import { + cancelBuild, + runBuildCommand, + startTestGenerationProcess, +} from '../../../codewhisperer/commands/startTestGeneration' +import { UserIntent } from '@amzn/codewhisperer-streaming' +import { getSelectedCustomization } from '../../../codewhisperer/util/customizationUtil' +import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient' +import { ChatTriggerType } from '../../../codewhispererChat/controllers/chat/model' +import { triggerPayloadToChatRequest } from '../../../codewhispererChat/controllers/chat/chatRequest/converter' +import { EditorContentController } from '../../../amazonq/commons/controllers/contentController' +import { amazonQTabSuffix } from '../../../shared/constants' +import { applyChanges } from '../../../shared/utilities/textDocumentUtilities' +import { telemetry } from '../../../shared/telemetry/telemetry' +import { CodeReference } from '../../../amazonq' +import { CodeWhispererSettings } from '../../../codewhisperer/util/codewhispererSettings' + +export interface TestChatControllerEventEmitters { + readonly tabOpened: vscode.EventEmitter + readonly tabClosed: vscode.EventEmitter + readonly authClicked: vscode.EventEmitter + readonly startTestGen: vscode.EventEmitter + readonly processHumanChatMessage: vscode.EventEmitter + readonly updateShortAnswer: vscode.EventEmitter + readonly showCodeGenerationResults: vscode.EventEmitter + readonly openDiff: vscode.EventEmitter + readonly formActionClicked: vscode.EventEmitter + readonly followUpClicked: vscode.EventEmitter + readonly sendUpdatePromptProgress: vscode.EventEmitter + readonly errorThrown: vscode.EventEmitter + readonly insertCodeAtCursorPosition: vscode.EventEmitter + readonly processResponseBodyLinkClick: vscode.EventEmitter +} + +type OpenDiffMessage = { + tabID: string + messageId: string + filePath: string + codeGenerationId: string +} + +export class TestController { + private readonly messenger: Messenger + private readonly sessionStorage: ChatSessionManager + private authController: AuthController + private readonly editorContentController: EditorContentController + tempResultDirPath = path.join(tempDirPath, 'q-testgen') + + public constructor( + private readonly chatControllerMessageListeners: TestChatControllerEventEmitters, + messenger: Messenger, + onDidChangeAmazonQVisibility: vscode.Event + ) { + this.messenger = messenger + this.sessionStorage = ChatSessionManager.Instance + this.authController = new AuthController() + this.editorContentController = new EditorContentController() + + this.chatControllerMessageListeners.tabOpened.event((data) => { + return this.tabOpened(data) + }) + + this.chatControllerMessageListeners.tabClosed.event((data) => { + return this.tabClosed(data) + }) + + this.chatControllerMessageListeners.authClicked.event((data) => { + this.authClicked(data) + }) + + this.chatControllerMessageListeners.startTestGen.event(async (data) => { + await this.startTestGen(data, false) + }) + + this.chatControllerMessageListeners.processHumanChatMessage.event((data) => { + return this.processHumanChatMessage(data) + }) + + this.chatControllerMessageListeners.formActionClicked.event((data) => { + return this.handleFormActionClicked(data) + }) + + this.chatControllerMessageListeners.updateShortAnswer.event((data) => { + return this.updateShortAnswer(data) + }) + + this.chatControllerMessageListeners.showCodeGenerationResults.event((data) => { + return this.showCodeGenerationResults(data) + }) + + this.chatControllerMessageListeners.openDiff.event((data) => { + return this.openDiff(data) + }) + + this.chatControllerMessageListeners.sendUpdatePromptProgress.event((data) => { + return this.handleUpdatePromptProgress(data) + }) + + this.chatControllerMessageListeners.errorThrown.event((data) => { + return this.handleErrorMessage(data) + }) + + this.chatControllerMessageListeners.insertCodeAtCursorPosition.event((data) => { + return this.handleInsertCodeAtCursorPosition(data) + }) + + this.chatControllerMessageListeners.processResponseBodyLinkClick.event((data) => { + return this.processLink(data) + }) + + this.chatControllerMessageListeners.followUpClicked.event((data) => { + switch (data.followUp.type) { + case FollowUpTypes.ViewDiff: + return this.openDiff(data) + case FollowUpTypes.AcceptCode: + return this.acceptCode(data) + case FollowUpTypes.RejectCode: + return this.endSession(data, FollowUpTypes.RejectCode) + case FollowUpTypes.ContinueBuildAndExecute: + return this.handleBuildIteration(data) + case FollowUpTypes.BuildAndExecute: + return this.checkForInstallationDependencies(data) + case FollowUpTypes.ModifyCommands: + return this.modifyBuildCommand(data) + case FollowUpTypes.SkipBuildAndFinish: + return this.endSession(data, FollowUpTypes.SkipBuildAndFinish) + case FollowUpTypes.InstallDependenciesAndContinue: + return this.handleInstallDependencies(data) + case FollowUpTypes.ViewCodeDiffAfterIteration: + return this.openDiff(data) + } + }) + } + + /** + * Basic Functions + */ + private async tabOpened(message: any) { + const session: Session = this.sessionStorage.getSession() + const tabID = this.sessionStorage.setActiveTab(message.tabID) + const logger = getLogger() + logger.debug('Tab opened Processing message tabId: %s', message.tabID) + + // check if authentication has expired + try { + logger.debug(`Q - Test: Session created with id: ${session.tabID}`) + + const authState = await AuthUtil.instance.getChatAuthState() + if (authState.amazonQ !== 'connected') { + void this.messenger.sendAuthNeededExceptionMessage(authState, tabID) + session.isAuthenticating = true + return + } + } catch (err: any) { + logger.error('tabOpened failed: %O', err) + this.messenger.sendErrorMessage(err.message, message.tabID) + } + } + + private async tabClosed(data: any) { + getLogger().debug('Tab closed with data tab id: %s', data.tabID) + await this.sessionCleanUp() + getLogger().debug('Removing active tab') + this.sessionStorage.removeActiveTab() + } + + private authClicked(message: any) { + this.authController.handleAuth(message.authType) + + this.messenger.sendMessage('Follow instructions to re-authenticate ...', message.tabID, 'answer') + + // Explicitly ensure the user goes through the re-authenticate flow + this.messenger.sendChatInputEnabled(message.tabID, false) + } + + private processLink(message: any) { + void openUrl(vscode.Uri.parse(message.link)) + } + + private handleInsertCodeAtCursorPosition(message: any) { + this.editorContentController.insertTextAtCursorPosition(message.code, () => {}) + } + + private checkCodeDiffLengthAndBuildStatus(state: { codeDiffLength: number; buildStatus: BuildStatus }): boolean { + return state.codeDiffLength !== 0 && state.buildStatus !== BuildStatus.SUCCESS + } + + // Displaying error message to the user in the chat tab + private async handleErrorMessage(data: any) { + testGenState.setToNotStarted() + // eslint-disable-next-line unicorn/no-null + this.messenger.sendUpdatePromptProgress(data.tabID, null) + const session = this.sessionStorage.getSession() + const isCancel = data.error.message === unitTestGenerationCancelMessage + + TelemetryHelper.instance.sendTestGenerationToolkitEvent( + session, + true, + isCancel ? 'Cancelled' : 'Failed', + session.startTestGenerationRequestId, + performance.now() - session.testGenerationStartTime, + getTelemetryReasonDesc(data.error), + session.isCodeBlockSelected, + session.artifactsUploadDuration, + session.srcPayloadSize, + session.srcZipFileSize + ) + + if (session.stopIteration) { + // Error from Science + this.messenger.sendMessage(data.error.message.replaceAll('```', ''), data.tabID, 'answer') + } else { + isCancel + ? this.messenger.sendMessage(data.error.message, data.tabID, 'answer') + : this.sendErrorMessage(data) + } + await this.sessionCleanUp() + return + } + // Client side error messages + private sendErrorMessage(data: { tabID: string; error: { code: string; message: string } }) { + const { error, tabID } = data + + if (isAwsError(error)) { + if (error.code === 'ThrottlingException') { + // TODO: use the explicitly modeled exception reason for quota vs throttle + if (error.message.includes(CodeWhispererConstants.utgLimitReached)) { + getLogger().error('Monthly quota reached for QSDA actions.') + return this.messenger.sendMessage( + i18n('AWS.amazonq.featureDev.error.monthlyLimitReached'), + tabID, + 'answer' + ) + } else { + getLogger().error('Too many requests.') + // TODO: move to constants file + this.messenger.sendErrorMessage('Too many requests. Please wait before retrying.', tabID) + } + } else { + // other service errors: + // AccessDeniedException - should not happen because access is validated before this point in the client + // ValidationException - shouldn't happen because client should not send malformed requests + // ConflictException - should not happen because the client will maintain proper state + // InternalServerException - shouldn't happen but needs to be caught + getLogger().error('Other error message: %s', error.message) + this.messenger.sendErrorMessage( + 'Encountered an unexpected error when generating tests. Please try again', + tabID + ) + } + } else { + // other unexpected errors (TODO enumerate all other failure cases) + getLogger().error('Other error message: %s', error.message) + this.messenger.sendErrorMessage( + 'Encountered an unexpected error when generating tests. Please try again', + tabID + ) + } + } + + // This function handles actions if user clicked on any Button one of these cases will be executed + private async handleFormActionClicked(data: any) { + const typedAction = MessengerUtils.stringToEnumValue(ButtonActions, data.action as any) + switch (typedAction) { + case ButtonActions.STOP_TEST_GEN: + testGenState.setToCancelling() + telemetry.ui_click.emit({ elementId: 'unitTestGeneration_cancelTestGenerationProgress' }) + break + case ButtonActions.STOP_BUILD: + cancelBuild() + void this.handleUpdatePromptProgress({ status: 'cancel', tabID: data.tabID }) + telemetry.ui_click.emit({ elementId: 'unitTestGeneration_cancelBuildProgress' }) + this.messenger.sendChatInputEnabled(data.tabID, true) + await this.sessionCleanUp() + break + } + } + // This function handles actions if user gives any input from the chatInput box + private async processHumanChatMessage(data: { prompt: string; tabID: string }) { + const session = this.sessionStorage.getSession() + const conversationState = session.conversationState + + if (conversationState === ConversationState.WAITING_FOR_BUILD_COMMMAND_INPUT) { + this.messenger.sendChatInputEnabled(data.tabID, false) + this.sessionStorage.getSession().conversationState = ConversationState.IDLE + session.updatedBuildCommands = [data.prompt] + const updatedCommands = session.updatedBuildCommands.join('\n') + this.messenger.sendMessage(`Updated command to \`${updatedCommands}\``, data.tabID, 'prompt') + await this.checkForInstallationDependencies(data) + return + } else { + await this.startTestGen(data, false) + } + } + // This function takes filePath as input parameter and returns file language + private async getLanguageForFilePath(filePath: string): Promise { + try { + const document = await vscode.workspace.openTextDocument(filePath) + return document.languageId + } catch (error) { + return 'plaintext' + } + } + + /** + * Start Test Generation and show the code results + */ + + private async startTestGen(message: any, regenerateTests: boolean) { + const session: Session = this.sessionStorage.getSession() + const tabID = this.sessionStorage.setActiveTab(message.tabID) + getLogger().debug('startTestGen message: %O', message) + getLogger().debug('startTestGen tabId: %O', message.tabID) + let fileName = '' + let filePath = '' + let userMessage = '' + session.testGenerationStartTime = performance.now() + + try { + if (ChatSessionManager.Instance.getIsInProgress()) { + void vscode.window.showInformationMessage( + "There is already a test generation job in progress. Cancel current job or wait until it's finished to try again." + ) + return + } + if (testGenState.isCancelling()) { + void vscode.window.showInformationMessage( + 'There is a test generation job being cancelled. Please wait for cancellation to finish.' + ) + return + } + + // check that the session is authenticated + const authState = await AuthUtil.instance.getChatAuthState() + if (authState.amazonQ !== 'connected') { + void this.messenger.sendAuthNeededExceptionMessage(authState, tabID) + session.isAuthenticating = true + return + } + + // check that a project/workspace is open + const workspaceFolders = vscode.workspace.workspaceFolders + if (workspaceFolders === undefined || workspaceFolders.length === 0) { + this.messenger.sendUnrecoverableErrorResponse('no-project-found', tabID) + return + } + + // check if IDE has active file open. + const activeEditor = vscode.window.activeTextEditor + // also check all open editors and allow this to proceed if only one is open (even if not main focus) + const allVisibleEditors = vscode.window.visibleTextEditors + const openFileEditors = allVisibleEditors.filter((editor) => editor.document.uri.scheme === 'file') + const hasOnlyOneOpenFileSplitView = openFileEditors.length === 1 + getLogger().debug(`hasOnlyOneOpenSplitView: ${hasOnlyOneOpenFileSplitView}`) + // is not a file if the currently highlighted window is not a file, and there is either more than one or no file windows open + const isNotFile = activeEditor?.document.uri.scheme !== 'file' && !hasOnlyOneOpenFileSplitView + getLogger().debug(`activeEditor: ${activeEditor}, isNotFile: ${isNotFile}`) + if (!activeEditor || isNotFile) { + this.messenger.sendUnrecoverableErrorResponse( + isNotFile ? 'invalid-file-type' : 'no-open-file-found', + tabID + ) + this.messenger.sendUpdatePlaceholder( + tabID, + 'Please open and highlight a source code file in order to generate tests.' + ) + this.messenger.sendChatInputEnabled(tabID, true) + this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_INPUT + return + } + + const fileEditorToTest = hasOnlyOneOpenFileSplitView ? openFileEditors[0] : activeEditor + getLogger().debug(`File path: ${fileEditorToTest.document.uri.fsPath}`) + filePath = fileEditorToTest.document.uri.fsPath + fileName = path.basename(filePath) + userMessage = message.prompt + ? regenerateTests + ? `${message.prompt}` + : `/test ${message.prompt}` + : `/test Generate unit tests for \`${fileName}\`` + + session.hasUserPromptSupplied = message.prompt.length > 0 + + // displaying user message prompt in Test tab + this.messenger.sendMessage(userMessage, tabID, 'prompt') + this.messenger.sendChatInputEnabled(tabID, false) + this.sessionStorage.getSession().conversationState = ConversationState.IN_PROGRESS + this.messenger.sendUpdatePromptProgress(message.tabID, testGenProgressField) + + const language = await this.getLanguageForFilePath(filePath) + session.fileLanguage = language + + /* + For Re:Invent 2024 we are supporting only java and python for unit test generation, rest of the languages shows the similar experience as CWC + */ + if (language !== 'java' && language !== 'python') { + const unsupportedLanguage = language.charAt(0).toUpperCase() + language.slice(1) + let unsupportedMessage = `I'm sorry, but /test only supports Python and Java
While ${unsupportedLanguage} is not supported, I will generate a suggestion below. ` + // handle the case when language is undefined + if (!unsupportedLanguage) { + unsupportedMessage = `I'm sorry, but /test only supports Python and Java
I will still generate a suggestion below. ` + } + this.messenger.sendMessage(unsupportedMessage, tabID, 'answer') + await this.onCodeGeneration(session, message.prompt, tabID, fileName, filePath) + } else { + this.messenger.sendCapabilityCard({ tabID }) + this.messenger.sendMessage(testGenSummaryMessage(fileName), message.tabID, 'answer-part') + + // Grab the selection from the fileEditorToTest and get the vscode Range + const selection = fileEditorToTest.selection + let selectionRange = undefined + if ( + selection.start.line !== selection.end.line || + selection.start.character !== selection.end.character + ) { + selectionRange = new vscode.Range( + selection.start.line, + selection.start.character, + selection.end.line, + selection.end.character + ) + } + session.isCodeBlockSelected = selectionRange !== undefined + + /** + * Zip the project + * Create pre-signed URL and upload artifact to S3 + * send API request to startTestGeneration API + * Poll from getTestGeneration API + * Get Diff from exportResultArchive API + */ + ChatSessionManager.Instance.setIsInProgress(true) + await startTestGenerationProcess(fileName, filePath, message.prompt, tabID, true, selectionRange) + } + } catch (err: any) { + // TODO: refactor error handling to be more robust + ChatSessionManager.Instance.setIsInProgress(false) + getLogger().error('startTestGen failed: %O', err) + this.messenger.sendUpdatePromptProgress(message.tabID, cancellingProgressField) + this.sendErrorMessage({ tabID, error: err }) + this.messenger.sendChatInputEnabled(tabID, true) + this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_INPUT + await sleep(2000) + // eslint-disable-next-line unicorn/no-null + this.messenger.sendUpdatePromptProgress(message.tabID, null) + } + } + + // Updating Progress bar + private async handleUpdatePromptProgress(data: any) { + const getProgressField = (status: string): ProgressField | null => { + switch (status) { + case 'Completed': + return testGenCompletedField + case 'Error': + return errorProgressField + case 'cancel': + return cancellingProgressField + case 'InProgress': + default: + return { + status: 'info', + text: 'Generating unit tests...', + value: data.progressRate, + valueText: data.progressRate.toString() + '%', + actions: [cancelTestGenButton], + } + } + } + this.messenger.sendUpdatePromptProgress(data.tabID, getProgressField(data.status)) + + await sleep(2000) + + // don't flash the bar when generation in progress + if (data.status !== 'InProgress') { + // eslint-disable-next-line unicorn/no-null + this.messenger.sendUpdatePromptProgress(data.tabID, null) + } + } + + private async updateShortAnswer(message: { + tabID: string + status: string + shortAnswer?: ShortAnswer + testGenerationJobGroupName: string + testGenerationJobId: string + type: ChatItemType + fileName: string + }) { + this.messenger.sendShortSummary({ + type: 'answer', + tabID: message.tabID, + message: testGenSummaryMessage(message.fileName, message.shortAnswer?.planSummary?.replaceAll('```', '')), + canBeVoted: true, + filePath: message.shortAnswer?.testFilePath, + }) + } + + private async showCodeGenerationResults(data: { tabID: string; filePath: string; projectName: string }) { + const session = this.sessionStorage.getSession() + // return early if references are disabled and there are references + if (!CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled() && session.references.length > 0) { + void vscode.window.showInformationMessage('Your settings do not allow code generation with references.') + await this.endSession(data, FollowUpTypes.SkipBuildAndFinish) + await this.sessionCleanUp() + return + } + const followUps: FollowUps = { + text: '', + options: [ + { + pillText: `View diff`, + type: FollowUpTypes.ViewDiff, + status: 'primary', + }, + ], + } + session.generatedFilePath = data.filePath + try { + const tempFilePath = path.join(this.tempResultDirPath, 'resultArtifacts', data.filePath) + const newContent = await fs.readFileText(tempFilePath) + const workspaceFolder = vscode.workspace.workspaceFolders?.[0] + let linesGenerated = newContent.split('\n').length + let charsGenerated = newContent.length + if (workspaceFolder) { + const projectPath = workspaceFolder.uri.fsPath + const absolutePath = path.join(projectPath, data.filePath) + const fileExists = await fs.existsFile(absolutePath) + if (fileExists) { + const originalContent = await fs.readFileText(absolutePath) + linesGenerated -= originalContent.split('\n').length + charsGenerated -= originalContent.length + } + } + session.linesOfCodeGenerated = linesGenerated > 0 ? linesGenerated : 0 + session.charsOfCodeGenerated = charsGenerated > 0 ? charsGenerated : 0 + } catch (e: any) { + getLogger().debug('failed to get chars and lines of code generated from test generation result: %O', e) + } + + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer', + codeGenerationId: '', + message: `Please see the unit tests generated below. Click “View diff” to review the changes in the code editor.`, + canBeVoted: true, + messageId: '', + followUps, + fileList: { + fileTreeTitle: 'READY FOR REVIEW', + rootFolderTitle: data.projectName, + filePaths: [data.filePath], + }, + codeReference: session.references.map( + (ref: ShortAnswerReference) => + ({ + ...ref, + information: `${ref.licenseName} - ${ref.repository}`, + }) as CodeReference + ), + }) + this.messenger.sendChatInputEnabled(data.tabID, false) + this.messenger.sendUpdatePlaceholder(data.tabID, `Select View diff to see the generated unit tests.`) + this.sessionStorage.getSession().conversationState = ConversationState.IDLE + } + + private async openDiff(message: OpenDiffMessage) { + const session = this.sessionStorage.getSession() + const filePath = session.generatedFilePath + const workspaceFolder = vscode.workspace.workspaceFolders?.[0] + if (!workspaceFolder) { + throw new Error('No workspace folder found') + } + const projectPath = workspaceFolder.uri.fsPath + const absolutePath = path.join(projectPath, filePath) + const fileExists = await fs.existsFile(absolutePath) + const leftUri = fileExists ? vscode.Uri.file(absolutePath) : vscode.Uri.from({ scheme: 'untitled' }) + const rightUri = vscode.Uri.file(path.join(this.tempResultDirPath, 'resultArtifacts', filePath)) + const fileName = path.basename(absolutePath) + await vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, `${fileName} ${amazonQTabSuffix}`) + telemetry.ui_click.emit({ elementId: 'unitTestGeneration_viewDiff' }) + session.latencyOfTestGeneration = performance.now() - session.testGenerationStartTime + this.messenger.sendUpdatePlaceholder(message.tabID, `Please select an action to proceed (Accept or Reject)`) + } + + private async acceptCode(message: any) { + const session = this.sessionStorage.getSession() + session.acceptedJobId = session.listOfTestGenerationJobId[session.listOfTestGenerationJobId.length - 1] + const filePath = session.generatedFilePath + const absolutePath = path.join(session.projectRootPath, filePath) + const fileExists = await fs.existsFile(absolutePath) + const buildCommand = session.updatedBuildCommands?.join(' ') + + const tempFilePath = path.join(this.tempResultDirPath, 'resultArtifacts', filePath) + const updatedContent = await fs.readFileText(tempFilePath) + let acceptedLines = updatedContent.split('\n').length + let acceptedChars = updatedContent.length + if (fileExists) { + const originalContent = await fs.readFileText(absolutePath) + acceptedLines -= originalContent.split('\n').length + acceptedLines = acceptedLines < 0 ? 0 : acceptedLines + acceptedChars -= originalContent.length + acceptedChars = acceptedChars < 0 ? 0 : acceptedChars + const document = await vscode.workspace.openTextDocument(absolutePath) + await applyChanges( + document, + new vscode.Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end), + updatedContent + ) + } else { + await fs.writeFile(absolutePath, updatedContent) + } + session.charsOfCodeAccepted = acceptedChars + session.linesOfCodeAccepted = acceptedLines + + // add accepted references to reference log, if any + const fileName = path.basename(session.generatedFilePath) + const time = new Date().toLocaleString() + // TODO: this is duplicated in basicCommands.ts for scan (codewhisperer). Fix this later. + session.references.forEach((reference) => { + getLogger().debug('Processing reference: %O', reference) + // Log values for debugging + getLogger().debug('updatedContent: %s', updatedContent) + getLogger().debug( + 'start: %d, end: %d', + reference.recommendationContentSpan?.start, + reference.recommendationContentSpan?.end + ) + // given a start and end index, figure out which line number they belong to when splitting a string on /n characters + const getLineNumber = (content: string, index: number): number => { + const lines = content.slice(0, index).split('\n') + return lines.length + } + const startLine = getLineNumber(updatedContent, reference.recommendationContentSpan!.start) + const endLine = getLineNumber(updatedContent, reference.recommendationContentSpan!.end) + getLogger().debug('startLine: %d, endLine: %d', startLine, endLine) + + const code = updatedContent.slice( + reference.recommendationContentSpan?.start, + reference.recommendationContentSpan?.end + ) + getLogger().debug('Extracted code slice: %s', code) + const referenceLog = + `[${time}] Accepted recommendation ` + + CodeWhispererConstants.referenceLogText( + `
${code}
`, + reference.licenseName!, + reference.repository!, + fileName, + startLine === endLine ? `(line at ${startLine})` : `(lines from ${startLine} to ${endLine})` + ) + + '
' + getLogger().debug('Adding reference log: %s', referenceLog) + ReferenceLogViewProvider.instance.addReferenceLog(referenceLog) + }) + + // TODO: see if there's a better way to check if active file is a diff + if (vscode.window.tabGroups.activeTabGroup.activeTab?.label.includes(amazonQTabSuffix)) { + await vscode.commands.executeCommand('workbench.action.closeActiveEditor') + } + const document = await vscode.workspace.openTextDocument(absolutePath) + await vscode.window.showTextDocument(document) + // TODO: send the message once again once build is enabled + // this.messenger.sendMessage('Accepted', message.tabID, 'prompt') + telemetry.ui_click.emit({ elementId: 'unitTestGeneration_acceptDiff' }) + + TelemetryHelper.instance.sendTestGenerationToolkitEvent( + session, + true, + 'Succeeded', + session.startTestGenerationRequestId, + session.latencyOfTestGeneration, + undefined, + session.isCodeBlockSelected, + session.artifactsUploadDuration, + session.srcPayloadSize, + session.srcZipFileSize, + session.charsOfCodeAccepted, + session.numberOfTestsGenerated, + session.linesOfCodeAccepted, + session.charsOfCodeGenerated, + session.numberOfTestsGenerated, + session.linesOfCodeGenerated + ) + + await this.endSession(message, FollowUpTypes.SkipBuildAndFinish) + await this.sessionCleanUp() + return + + if (session.listOfTestGenerationJobId.length === 1) { + this.startInitialBuild(message) + this.messenger.sendChatInputEnabled(message.tabID, false) + } else if (session.listOfTestGenerationJobId.length < 4) { + const remainingIterations = 4 - session.listOfTestGenerationJobId.length + + let userMessage = 'Would you like Amazon Q to build and execute again, and fix errors?' + if (buildCommand) { + userMessage += ` I will be running this build command: \`${buildCommand}\`` + } + userMessage += `\nYou have ${remainingIterations} iteration${remainingIterations > 1 ? 's' : ''} left.` + + const followUps: FollowUps = { + text: '', + options: [ + { + pillText: `Rebuild`, + type: FollowUpTypes.ContinueBuildAndExecute, + status: 'primary', + }, + { + pillText: `Skip and finish`, + type: FollowUpTypes.SkipBuildAndFinish, + status: 'primary', + }, + ], + } + this.messenger.sendBuildProgressMessage({ + tabID: message.tabID, + messageType: 'answer', + codeGenerationId: '', + message: userMessage, + canBeVoted: false, + messageId: '', + followUps: followUps, + }) + this.messenger.sendChatInputEnabled(message.tabID, false) + } else { + this.sessionStorage.getSession().listOfTestGenerationJobId = [] + this.messenger.sendMessage( + 'You have gone through both iterations and this unit test generation workflow is complete.', + message.tabID, + 'answer' + ) + await this.sessionCleanUp() + } + await fs.delete(this.tempResultDirPath, { recursive: true }) + } + + /** + * Handle a regular incoming message when a user is in the code generation phase + */ + private async onCodeGeneration( + session: Session, + message: string, + tabID: string, + fileName: string, + filePath: string + ) { + try { + // TODO: Write this entire gen response to basiccommands and call here. + const editorText = await fs.readFileText(filePath) + + const triggerPayload = { + query: `Generate unit tests for the following part of my code: ${message}`, + codeSelection: undefined, + trigger: ChatTriggerType.ChatMessage, + fileText: editorText, + fileLanguage: session.fileLanguage, + filePath: filePath, + message: `Generate unit tests for the following part of my code: ${message}`, + matchPolicy: undefined, + codeQuery: undefined, + userIntent: UserIntent.GENERATE_UNIT_TESTS, + customization: getSelectedCustomization(), + } + const chatRequest = triggerPayloadToChatRequest(triggerPayload) + const client = await createCodeWhispererChatStreamingClient() + const response = await client.generateAssistantResponse(chatRequest) + await this.messenger.sendAIResponse( + response, + session, + tabID, + randomUUID.toString(), + triggerPayload, + fileName + ) + } finally { + this.messenger.sendChatInputEnabled(tabID, true) + this.messenger.sendUpdatePlaceholder(tabID, `/test Generate unit tests...`) + this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_INPUT + } + } + + // TODO: Check if there are more cases to endSession if yes create a enum or type for step + private async endSession(data: any, step: FollowUpTypes) { + const session = this.sessionStorage.getSession() + if (step === FollowUpTypes.RejectCode) { + TelemetryHelper.instance.sendTestGenerationToolkitEvent( + session, + true, + 'Succeeded', + session.startTestGenerationRequestId, + session.latencyOfTestGeneration, + undefined, + session.isCodeBlockSelected, + session.artifactsUploadDuration, + session.srcPayloadSize, + session.srcZipFileSize, + 0, + 0, + 0, + session.charsOfCodeGenerated, + session.numberOfTestsGenerated, + session.linesOfCodeGenerated + ) + + telemetry.ui_click.emit({ elementId: 'unitTestGeneration_rejectDiff' }) + } + + await this.sessionCleanUp() + // TODO: revert 'Accepted' to 'Skip build and finish' once supported + const message = step === FollowUpTypes.RejectCode ? 'Rejected' : 'Accepted' + + this.messenger.sendMessage(message, data.tabID, 'prompt') + this.messenger.sendMessage(`Unit test generation workflow is completed.`, data.tabID, 'answer') + this.messenger.sendChatInputEnabled(data.tabID, true) + return + } + + /** + * BUILD LOOP IMPLEMENTATION + */ + + private startInitialBuild(data: any) { + // TODO: Remove the fallback build command after stable version of backend build command. + const userMessage = `Would you like me to help build and execute the test? I will need you to let me know what build command to run if you do.` + const followUps: FollowUps = { + text: '', + options: [ + { + pillText: `Specify command then build and execute`, + type: FollowUpTypes.ModifyCommands, + status: 'primary', + }, + { + pillText: `Skip and finish`, + type: FollowUpTypes.SkipBuildAndFinish, + status: 'primary', + }, + ], + } + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer', + codeGenerationId: '', + message: userMessage, + canBeVoted: false, + messageId: '', + followUps: followUps, + }) + this.messenger.sendChatInputEnabled(data.tabID, false) + } + + private async checkForInstallationDependencies(data: any) { + // const session: Session = this.sessionStorage.getSession() + // const listOfInstallationDependencies = session.testGenerationJob?.shortAnswer?.installationDependencies || [] + // MOCK: As there is no installation dependencies in shortAnswer + const listOfInstallationDependencies = [''] + const installationDependencies = listOfInstallationDependencies.join('\n') + + this.messenger.sendMessage('Build and execute', data.tabID, 'prompt') + telemetry.ui_click.emit({ elementId: 'unitTestGeneration_buildAndExecute' }) + + if (installationDependencies.length > 0) { + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer', + codeGenerationId: '', + message: `Looks like you don’t have ${listOfInstallationDependencies.length > 1 ? `these` : `this`} ${listOfInstallationDependencies.length} required package${listOfInstallationDependencies.length > 1 ? `s` : ``} installed.\n\`\`\`sh\n${installationDependencies}\n`, + canBeVoted: false, + messageId: '', + followUps: { + text: '', + options: [ + { + pillText: `Install and continue`, + type: FollowUpTypes.InstallDependenciesAndContinue, + status: 'primary', + }, + { + pillText: `Skip and finish`, + type: FollowUpTypes.SkipBuildAndFinish, + status: 'primary', + }, + ], + }, + }) + } else { + await this.startLocalBuildExecution(data) + } + } + + private async handleInstallDependencies(data: any) { + this.messenger.sendMessage('Installation dependencies and continue', data.tabID, 'prompt') + telemetry.ui_click.emit({ elementId: 'unitTestGeneration_installDependenciesAndContinue' }) + void this.startLocalBuildExecution(data) + } + + private async handleBuildIteration(data: any) { + this.messenger.sendMessage('Proceed with Iteration', data.tabID, 'prompt') + telemetry.ui_click.emit({ elementId: 'unitTestGeneration_proceedWithIteration' }) + await this.startLocalBuildExecution(data) + } + + private async startLocalBuildExecution(data: any) { + const session: Session = this.sessionStorage.getSession() + // const installationDependencies = session.shortAnswer?.installationDependencies ?? [] + // MOCK: ignoring the installation case until backend send response + const installationDependencies: string[] = [] + const buildCommands = session.updatedBuildCommands + if (!buildCommands) { + throw new Error('Build command not found') + return + } + + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.START_STEP), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + + this.messenger.sendUpdatePromptProgress(data.tabID, buildProgressField) + + if (installationDependencies.length > 0 && session.listOfTestGenerationJobId.length < 2) { + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.INSTALL_DEPENDENCIES, 'current'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + + const status = await runBuildCommand(installationDependencies) + // TODO: Add separate status for installation dependencies + session.buildStatus = status + if (status === BuildStatus.FAILURE) { + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.INSTALL_DEPENDENCIES, 'error'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + } + if (status === BuildStatus.CANCELLED) { + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.INSTALL_DEPENDENCIES, 'error'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + this.messenger.sendMessage('Installation dependencies Cancelled', data.tabID, 'prompt') + this.messenger.sendMessage( + 'Unit test generation workflow is complete. You have 25 out of 30 Amazon Q Developer Agent invocations left this month.', + data.tabID, + 'answer' + ) + return + } + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.INSTALL_DEPENDENCIES, 'done'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + } + + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.RUN_BUILD, 'current'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + + const buildStatus = await runBuildCommand(buildCommands) + session.buildStatus = buildStatus + + if (buildStatus === BuildStatus.FAILURE) { + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.RUN_BUILD, 'error'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + } else if (buildStatus === BuildStatus.CANCELLED) { + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.RUN_BUILD, 'error'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + this.messenger.sendMessage('Build Cancelled', data.tabID, 'prompt') + this.messenger.sendMessage('Unit test generation workflow is complete.', data.tabID, 'answer') + return + } else { + // Build successful + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.RUN_BUILD, 'done'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + } + + // Running execution tests + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.RUN_EXECUTION_TESTS, 'current'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + // After running tests + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.RUN_EXECUTION_TESTS, 'done'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + if (session.buildStatus !== BuildStatus.SUCCESS) { + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.FIXING_TEST_CASES, 'current'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + await startTestGenerationProcess( + path.basename(session.sourceFilePath), + session.sourceFilePath, + '', + data.tabID, + false + ) + } + // TODO: Skip this if startTestGenerationProcess timeouts + if (session.generatedFilePath) { + await this.showTestCaseSummary(data) + } + } + + private async showTestCaseSummary(data: { tabID: string }) { + const session: Session = this.sessionStorage.getSession() + let codeDiffLength = 0 + if (session.buildStatus !== BuildStatus.SUCCESS) { + // Check the generated test file content, if fileContent length is 0, exit the unit test generation workflow. + const tempFilePath = path.join(this.tempResultDirPath, 'resultArtifacts', session.generatedFilePath) + const codeDiffFileContent = await fs.readFileText(tempFilePath) + codeDiffLength = codeDiffFileContent.length + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.FIXING_TEST_CASES + 1, 'done'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + } + + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.PROCESS_TEST_RESULTS, 'current'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.PROCESS_TEST_RESULTS, 'done'), + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + }) + + const followUps: FollowUps = { + text: '', + options: [ + { + pillText: `View diff`, + type: FollowUpTypes.ViewCodeDiffAfterIteration, + status: 'primary', + }, + ], + } + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer-part', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: testGenBuildProgressMessage(TestGenerationBuildStep.PROCESS_TEST_RESULTS + 1), + canBeVoted: true, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + followUps: undefined, + fileList: this.checkCodeDiffLengthAndBuildStatus({ codeDiffLength, buildStatus: session.buildStatus }) + ? { + fileTreeTitle: 'READY FOR REVIEW', + rootFolderTitle: 'tests', + filePaths: [session.generatedFilePath], + } + : undefined, + }) + this.messenger.sendBuildProgressMessage({ + tabID: data.tabID, + messageType: 'answer', + codeGenerationId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + message: undefined, + canBeVoted: false, + messageId: TestNamedMessages.TEST_GENERATION_BUILD_STATUS_MESSAGE, + followUps: this.checkCodeDiffLengthAndBuildStatus({ codeDiffLength, buildStatus: session.buildStatus }) + ? followUps + : undefined, + fileList: undefined, + }) + + this.messenger.sendUpdatePromptProgress(data.tabID, testGenCompletedField) + await sleep(2000) + // eslint-disable-next-line unicorn/no-null + this.messenger.sendUpdatePromptProgress(data.tabID, null) + this.messenger.sendChatInputEnabled(data.tabID, false) + + if (codeDiffLength === 0 || session.buildStatus === BuildStatus.SUCCESS) { + this.messenger.sendMessage('Unit test generation workflow is complete.', data.tabID, 'answer') + await this.sessionCleanUp() + } + } + + private modifyBuildCommand(data: any) { + this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_BUILD_COMMMAND_INPUT + this.messenger.sendMessage('Specify commands then build', data.tabID, 'prompt') + telemetry.ui_click.emit({ elementId: 'unitTestGeneration_modifyCommand' }) + this.messenger.sendMessage( + 'Sure, provide all command lines you’d like me to run to build.', + data.tabID, + 'answer' + ) + this.messenger.sendUpdatePlaceholder(data.tabID, 'Waiting on your Inputs') + this.messenger.sendChatInputEnabled(data.tabID, true) + } + + /** Perform Session CleanUp in below cases + * UTG success + * End Session with Reject or SkipAndFinish + * After finishing 3 build loop iterations + * Error while generating unit tests + * Closing a Q-Test tab + * Progress bar cancel + */ + private async sessionCleanUp() { + const session = this.sessionStorage.getSession() + const groupName = session.testGenerationJobGroupName + const filePath = session.generatedFilePath + getLogger().debug('Entering sessionCleanUp function with filePath: %s and groupName: %s', filePath, groupName) + + vscode.window.tabGroups.all.flatMap(({ tabs }) => + tabs.map((tab) => { + if (tab.label === `${path.basename(filePath)} ${amazonQTabSuffix}`) { + const tabClosed = vscode.window.tabGroups.close(tab) + if (!tabClosed) { + getLogger().error('ChatDiff: Unable to close the diff view tab for %s', tab.label) + } + } + }) + ) + + getLogger().debug( + 'listOfTestGenerationJobId length: %d, groupName: %s', + session.listOfTestGenerationJobId.length, + groupName + ) + if (session.listOfTestGenerationJobId.length && groupName) { + session.listOfTestGenerationJobId.forEach((id) => { + if (id === session.acceptedJobId) { + TelemetryHelper.instance.sendTestGenerationEvent( + groupName, + id, + session.fileLanguage, + session.numberOfTestsGenerated, + session.numberOfTestsGenerated, // this is number of accepted test cases, now they can only accept all + session.linesOfCodeGenerated, + session.linesOfCodeAccepted, + session.charsOfCodeGenerated, + session.charsOfCodeAccepted + ) + } else { + TelemetryHelper.instance.sendTestGenerationEvent( + groupName, + id, + session.fileLanguage, + session.numberOfTestsGenerated, + 0, + session.linesOfCodeGenerated, + 0, + session.charsOfCodeGenerated, + 0 + ) + } + }) + } + session.listOfTestGenerationJobId = [] + session.testGenerationJobGroupName = undefined + session.testGenerationJob = undefined + session.updatedBuildCommands = undefined + session.shortAnswer = undefined + session.testCoveragePercentage = 0 + session.conversationState = ConversationState.IDLE + session.sourceFilePath = '' + session.generatedFilePath = '' + session.projectRootPath = '' + session.stopIteration = false + session.fileLanguage = undefined + ChatSessionManager.Instance.setIsInProgress(false) + session.linesOfCodeGenerated = 0 + session.linesOfCodeAccepted = 0 + session.charsOfCodeGenerated = 0 + session.charsOfCodeAccepted = 0 + session.acceptedJobId = '' + session.numberOfTestsGenerated = 0 + if (session.tabID) { + getLogger().debug('Setting input state with tabID: %s', session.tabID) + this.messenger.sendChatInputEnabled(session.tabID, true) + this.messenger.sendUpdatePlaceholder(session.tabID, '/test Generate unit tests') // TODO: Change according to the UX + } + getLogger().debug( + 'Deleting output.log and temp result directory. testGenerationLogsDir: %s', + testGenerationLogsDir + ) + await fs.delete(path.join(testGenerationLogsDir, 'output.log')) + await fs.delete(this.tempResultDirPath, { recursive: true }) + } + + // TODO: return build command when product approves + // private getBuildCommands = (): string[] => { + // const session = this.sessionStorage.getSession() + // if (session.updatedBuildCommands?.length) { + // return [...session.updatedBuildCommands] + // } + + // // For Internal amazon users only + // if (Auth.instance.isInternalAmazonUser()) { + // return ['brazil-build release'] + // } + + // if (session.shortAnswer && Array.isArray(session.shortAnswer?.buildCommands)) { + // return [...session.shortAnswer.buildCommands] + // } + + // return ['source qdev-wbr/.venv/bin/activate && pytest --continue-on-collection-errors'] + // } +} diff --git a/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts new file mode 100644 index 00000000000..10b496b69d3 --- /dev/null +++ b/packages/core/src/amazonqTest/chat/controller/messenger/messenger.ts @@ -0,0 +1,337 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + * This class controls the presentation of the various chat bubbles presented by the + * Q Test. + * + * As much as possible, all strings used in the experience should originate here. + */ + +import { AuthFollowUpType, AuthMessageDataMap } from '../../../../amazonq/auth/model' +import { FeatureAuthState } from '../../../../codewhisperer/util/authUtil' +import { + AppToWebViewMessageDispatcher, + AuthNeededException, + AuthenticationUpdateMessage, + BuildProgressMessage, + CapabilityCardMessage, + ChatInputEnabledMessage, + ChatMessage, + ChatSummaryMessage, + ErrorMessage, + UpdatePlaceholderMessage, + UpdatePromptProgressMessage, +} from '../../views/connector/connector' +import { ChatItemType } from '../../../../amazonq/commons/model' +import { ChatItemAction, ProgressField } from '@aws/mynah-ui' +import * as CodeWhispererConstants from '../../../../codewhisperer/models/constants' +import { TriggerPayload } from '../../../../codewhispererChat/controllers/chat/model' +import { + CodeWhispererStreamingServiceException, + GenerateAssistantResponseCommandOutput, +} from '@amzn/codewhisperer-streaming' +import { Session } from '../../session/session' +import { CodeReference } from '../../../../amazonq/webview/ui/apps/amazonqCommonsConnector' +import { getHttpStatusCode, getRequestId, getTelemetryReasonDesc, ToolkitError } from '../../../../shared/errors' +import { sleep, waitUntil } from '../../../../shared/utilities/timeoutUtils' +import { keys } from '../../../../shared/utilities/tsUtils' +import { TelemetryHelper, testGenState } from '../../../../codewhisperer' +import { cancellingProgressField, testGenCompletedField } from '../../../models/constants' + +export type UnrecoverableErrorType = 'no-project-found' | 'no-open-file-found' | 'invalid-file-type' + +export enum TestNamedMessages { + TEST_GENERATION_BUILD_STATUS_MESSAGE = 'testGenerationBuildStatusMessage', +} + +export interface FollowUps { + text?: string + options?: ChatItemAction[] +} + +export interface FileList { + fileTreeTitle?: string + rootFolderTitle?: string + filePaths?: string[] +} + +export interface SendBuildProgressMessageParams { + tabID: string + messageType: ChatItemType + codeGenerationId: string + message?: string + canBeVoted: boolean + messageId?: string + followUps?: FollowUps + fileList?: FileList + codeReference?: CodeReference[] +} + +export class Messenger { + public constructor(private readonly dispatcher: AppToWebViewMessageDispatcher) {} + + public sendCapabilityCard(params: { tabID: string }) { + this.dispatcher.sendChatMessage(new CapabilityCardMessage(params.tabID)) + } + + public sendMessage(message: string, tabID: string, messageType: ChatItemType) { + this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType }, tabID)) + } + + public sendShortSummary(params: { + message?: string + type: ChatItemType + tabID: string + messageID?: string + canBeVoted?: boolean + filePath?: string + }) { + this.dispatcher.sendChatSummaryMessage( + new ChatSummaryMessage( + { + message: params.message, + messageType: params.type, + messageId: params.messageID, + canBeVoted: params.canBeVoted, + filePath: params.filePath, + }, + params.tabID + ) + ) + } + + public sendChatInputEnabled(tabID: string, enabled: boolean) { + this.dispatcher.sendChatInputEnabled(new ChatInputEnabledMessage(tabID, enabled)) + } + + public sendUpdatePlaceholder(tabID: string, newPlaceholder: string) { + this.dispatcher.sendUpdatePlaceholder(new UpdatePlaceholderMessage(tabID, newPlaceholder)) + } + + public sendUpdatePromptProgress(tabID: string, progressField: ProgressField | null) { + this.dispatcher.sendUpdatePromptProgress(new UpdatePromptProgressMessage(tabID, progressField)) + } + + public async sendAuthNeededExceptionMessage(credentialState: FeatureAuthState, tabID: string) { + let authType: AuthFollowUpType = 'full-auth' + let message = AuthMessageDataMap[authType].message + + switch (credentialState.amazonQ) { + case 'disconnected': + authType = 'full-auth' + message = AuthMessageDataMap[authType].message + break + case 'unsupported': + authType = 'use-supported-auth' + message = AuthMessageDataMap[authType].message + break + case 'expired': + authType = 're-auth' + message = AuthMessageDataMap[authType].message + break + } + + this.dispatcher.sendAuthNeededExceptionMessage(new AuthNeededException(message, authType, tabID)) + } + + public sendAuthenticationUpdate(testEnabled: boolean, authenticatingTabIDs: string[]) { + this.dispatcher.sendAuthenticationUpdate(new AuthenticationUpdateMessage(testEnabled, authenticatingTabIDs)) + } + + /** + * This method renders an error message with a button at the end that will try the + * transformation again from the beginning. This message is meant for errors that are + * completely unrecoverable: the job cannot be completed in its current state, + * and the flow must be tried again. + */ + public sendUnrecoverableErrorResponse(type: UnrecoverableErrorType, tabID: string) { + let message = '...' + switch (type) { + case 'no-project-found': + message = CodeWhispererConstants.noOpenProjectsFoundChatTestGenMessage + break + case 'no-open-file-found': + message = CodeWhispererConstants.noOpenFileFoundChatMessage + break + case 'invalid-file-type': + message = CodeWhispererConstants.invalidFileTypeChatMessage + break + } + + this.dispatcher.sendChatMessage( + new ChatMessage( + { + message, + messageType: 'answer-stream', + }, + tabID + ) + ) + } + + public sendErrorMessage(errorMessage: string, tabID: string) { + this.dispatcher.sendErrorMessage( + new ErrorMessage(CodeWhispererConstants.genericErrorMessage, errorMessage, tabID) + ) + } + + // To show the response of unsupported languages to the user in the Q-Test tab + public async sendAIResponse( + response: GenerateAssistantResponseCommandOutput, + session: Session, + tabID: string, + triggerID: string, + triggerPayload: TriggerPayload, + fileName: string + ) { + let message = '' + let messageId = response.$metadata.requestId ?? '' + let codeReference: CodeReference[] = [] + + if (response.generateAssistantResponseResponse === undefined) { + throw new ToolkitError( + `Empty response from Q Developer service. Request ID: ${response.$metadata.requestId}` + ) + } + + const eventCounts = new Map() + waitUntil( + async () => { + for await (const chatEvent of response.generateAssistantResponseResponse!) { + for (const key of keys(chatEvent)) { + if ((chatEvent[key] as any) !== undefined) { + eventCounts.set(key, (eventCounts.get(key) ?? 0) + 1) + } + } + + if ( + chatEvent.codeReferenceEvent?.references !== undefined && + chatEvent.codeReferenceEvent.references.length > 0 + ) { + codeReference = [ + ...codeReference, + ...chatEvent.codeReferenceEvent.references.map((reference) => ({ + ...reference, + recommendationContentSpan: { + start: reference.recommendationContentSpan?.start ?? 0, + end: reference.recommendationContentSpan?.end ?? 0, + }, + information: `Reference code under **${reference.licenseName}** license from repository \`${reference.repository}\``, + })), + ] + } + if (testGenState.isCancelling()) { + return true + } + if ( + chatEvent.assistantResponseEvent?.content !== undefined && + chatEvent.assistantResponseEvent.content.length > 0 + ) { + message += chatEvent.assistantResponseEvent.content + this.dispatcher.sendBuildProgressMessage( + new BuildProgressMessage({ + tabID, + messageType: 'answer-part', + codeGenerationId: '', + message, + canBeVoted: false, + messageId, + followUps: undefined, + fileList: undefined, + }) + ) + } + } + return true + }, + { timeout: 60000, truthy: true } + ) + .catch((error: any) => { + let errorMessage = 'Error reading chat stream.' + let statusCode = undefined + let requestID = undefined + if (error instanceof CodeWhispererStreamingServiceException) { + errorMessage = error.message + statusCode = getHttpStatusCode(error) ?? 0 + requestID = getRequestId(error) + } + let message = 'This error is reported to the team automatically. Please try sending your message again.' + if (errorMessage !== undefined) { + message += `\n\nDetails: ${errorMessage}` + } + + if (statusCode !== undefined) { + message += `\n\nStatus Code: ${statusCode}` + } + + if (requestID !== undefined) { + messageId = requestID + message += `\n\nRequest ID: ${requestID}` + } + this.sendMessage(message.trim(), tabID, 'answer') + }) + .finally(async () => { + if (testGenState.isCancelling()) { + this.sendMessage(CodeWhispererConstants.unitTestGenerationCancelMessage, tabID, 'answer') + TelemetryHelper.instance.sendTestGenerationToolkitEvent( + session, + false, + 'Cancelled', + messageId, + performance.now() - session.testGenerationStartTime, + getTelemetryReasonDesc(CodeWhispererConstants.unitTestGenerationCancelMessage) + ) + + this.dispatcher.sendUpdatePromptProgress( + new UpdatePromptProgressMessage(tabID, cancellingProgressField) + ) + await sleep(500) + } else { + TelemetryHelper.instance.sendTestGenerationToolkitEvent( + session, + false, + 'Succeeded', + messageId, + performance.now() - session.testGenerationStartTime + ) + + this.dispatcher.sendUpdatePromptProgress( + new UpdatePromptProgressMessage(tabID, testGenCompletedField) + ) + await sleep(500) + } + testGenState.setToNotStarted() + // eslint-disable-next-line unicorn/no-null + this.dispatcher.sendUpdatePromptProgress(new UpdatePromptProgressMessage(tabID, null)) + }) + } + + // To show the Build progress in the chat + public sendBuildProgressMessage(params: SendBuildProgressMessageParams) { + const { + tabID, + messageType, + codeGenerationId, + message, + canBeVoted, + messageId, + followUps, + fileList, + codeReference, + } = params + this.dispatcher.sendBuildProgressMessage( + new BuildProgressMessage({ + tabID, + messageType, + codeGenerationId, + message, + canBeVoted, + messageId, + followUps, + fileList, + codeReference, + }) + ) + } +} diff --git a/packages/core/src/amazonqTest/chat/controller/messenger/messengerUtils.ts b/packages/core/src/amazonqTest/chat/controller/messenger/messengerUtils.ts new file mode 100644 index 00000000000..9e8fec4594e --- /dev/null +++ b/packages/core/src/amazonqTest/chat/controller/messenger/messengerUtils.ts @@ -0,0 +1,30 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + */ + +// These enums map to string IDs +export enum ButtonActions { + ACCEPT = 'Accept', + MODIFY = 'Modify', + REJECT = 'Reject', + VIEW_DIFF = 'View-Diff', + STOP_TEST_GEN = 'Stop-Test-Generation', + STOP_BUILD = 'Stop-Build-Process', +} + +// TODO: Refactor the common functionality between Transform, FeatureDev, CWSPRChat, Scan and UTG to a new Folder. + +export default class MessengerUtils { + static stringToEnumValue = ( + enumObject: T, + value: `${T[K]}` + ): T[K] => { + if (Object.values(enumObject).includes(value)) { + return value as unknown as T[K] + } else { + throw new Error('Value provided was not found in Enum') + } + } +} diff --git a/packages/core/src/amazonqTest/chat/session/session.ts b/packages/core/src/amazonqTest/chat/session/session.ts new file mode 100644 index 00000000000..713b8fcf5f7 --- /dev/null +++ b/packages/core/src/amazonqTest/chat/session/session.ts @@ -0,0 +1,74 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ShortAnswer, ShortAnswerReference } from '../../../codewhisperer' +import { TestGenerationJob } from '../../../codewhisperer/client/codewhispereruserclient' + +export enum ConversationState { + IDLE, + JOB_SUBMITTED, + WAITING_FOR_INPUT, + WAITING_FOR_BUILD_COMMMAND_INPUT, + WAITING_FOR_REGENERATE_INPUT, + IN_PROGRESS, +} + +export enum BuildStatus { + SUCCESS, + FAILURE, + CANCELLED, +} + +export class Session { + // Used to keep track of whether or not the current session is currently authenticating/needs authenticating + public isAuthenticating: boolean = false + + // A tab may or may not be currently open + public tabID: string | undefined + + // This is unique per each test generation cycle + public testGenerationJobGroupName: string | undefined = undefined + public listOfTestGenerationJobId: string[] = [] + public startTestGenerationRequestId: string | undefined = undefined + public testGenerationJob: TestGenerationJob | undefined + + // Start Test generation + public conversationState: ConversationState = ConversationState.IDLE + public shortAnswer: ShortAnswer | undefined + public sourceFilePath: string = '' + public generatedFilePath: string = '' + public projectRootPath: string = '' + public fileLanguage: string | undefined = 'plaintext' + public stopIteration: boolean = false + + // Telemetry + public testGenerationStartTime: number = 0 + public hasUserPromptSupplied: boolean = false + public isCodeBlockSelected: boolean = false + public srcPayloadSize: number = 0 + public srcZipFileSize: number = 0 + public artifactsUploadDuration: number = 0 + public numberOfTestsGenerated: number = 0 + public linesOfCodeGenerated: number = 0 + public linesOfCodeAccepted: number = 0 + public charsOfCodeGenerated: number = 0 + public charsOfCodeAccepted: number = 0 + public latencyOfTestGeneration: number = 0 + + // TODO: Take values from ShortAnswer or TestGenerationJob + // Build loop + public buildStatus: BuildStatus = BuildStatus.SUCCESS + public updatedBuildCommands: string[] | undefined = undefined + public testCoveragePercentage: number = 90 + public isInProgress: boolean = false + public acceptedJobId = '' + public references: ShortAnswerReference[] = [] + + constructor() {} + + public isTabOpen(): boolean { + return this.tabID !== undefined + } +} diff --git a/packages/core/src/amazonqTest/chat/storages/chatSession.ts b/packages/core/src/amazonqTest/chat/storages/chatSession.ts new file mode 100644 index 00000000000..99ef0a3b12e --- /dev/null +++ b/packages/core/src/amazonqTest/chat/storages/chatSession.ts @@ -0,0 +1,61 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + * + */ + +import { Session } from '../session/session' +import { getLogger } from '../../../shared/logger' + +export class SessionNotFoundError extends Error {} + +export class ChatSessionManager { + private static _instance: ChatSessionManager + private activeSession: Session | undefined + private isInProgress: boolean = false + + constructor() {} + + public static get Instance() { + return this._instance || (this._instance = new this()) + } + + private createSession(): Session { + this.activeSession = new Session() + return this.activeSession + } + + public getSession(): Session { + if (this.activeSession === undefined) { + return this.createSession() + } + + return this.activeSession + } + + public getIsInProgress(): boolean { + return this.isInProgress + } + + public setIsInProgress(value: boolean): void { + this.isInProgress = value + } + + public setActiveTab(tabID: string): string { + getLogger().debug(`Setting active tab: ${tabID}, activeSession: ${this.activeSession}`) + if (this.activeSession !== undefined) { + this.activeSession.tabID = tabID + return tabID + } + + throw new SessionNotFoundError() + } + + public removeActiveTab(): void { + getLogger().debug(`Removing active tab and deleting activeSession: ${this.activeSession}`) + if (this.activeSession !== undefined) { + this.activeSession.tabID = undefined + this.activeSession = undefined + } + } +} diff --git a/packages/core/src/amazonqTest/chat/views/actions/uiMessageListener.ts b/packages/core/src/amazonqTest/chat/views/actions/uiMessageListener.ts new file mode 100644 index 00000000000..967672105be --- /dev/null +++ b/packages/core/src/amazonqTest/chat/views/actions/uiMessageListener.ts @@ -0,0 +1,143 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MessageListener } from '../../../../amazonq/messages/messageListener' +import { ExtensionMessage } from '../../../../amazonq/webview/ui/commands' +import { TestChatControllerEventEmitters } from '../../controller/controller' + +type UIMessage = ExtensionMessage & { + tabID?: string +} + +export interface UIMessageListenerProps { + readonly chatControllerEventEmitters: TestChatControllerEventEmitters + readonly webViewMessageListener: MessageListener +} + +export class UIMessageListener { + private testControllerEventsEmitters: TestChatControllerEventEmitters | undefined + private webViewMessageListener: MessageListener + + constructor(props: UIMessageListenerProps) { + this.testControllerEventsEmitters = props.chatControllerEventEmitters + this.webViewMessageListener = props.webViewMessageListener + + // Now we are listening to events that get sent from amazonq/webview/actions/actionListener (e.g. the tab) + this.webViewMessageListener.onMessage((msg) => { + this.handleMessage(msg) + }) + } + + private handleMessage(msg: ExtensionMessage) { + switch (msg.command) { + case 'new-tab-was-created': + this.tabOpened(msg) + break + case 'tab-was-removed': + this.tabClosed(msg) + break + case 'auth-follow-up-was-clicked': + this.authClicked(msg) + break + case 'start-test-gen': + this.startTestGen(msg) + break + case 'chat-prompt': + this.processChatPrompt(msg) + break + case 'form-action-click': + this.formActionClicked(msg) + break + case 'follow-up-was-clicked': + this.followUpClicked(msg) + break + case 'open-diff': + this.openDiff(msg) + break + case 'insert_code_at_cursor_position': + this.insertCodeAtCursorPosition(msg) + break + case 'response-body-link-click': + this.processResponseBodyLinkClick(msg) + break + } + } + + private tabOpened(msg: UIMessage) { + this.testControllerEventsEmitters?.tabOpened.fire({ + tabID: msg.tabID, + }) + } + + private tabClosed(msg: UIMessage) { + this.testControllerEventsEmitters?.tabClosed.fire({ + tabID: msg.tabID, + }) + } + + private authClicked(msg: UIMessage) { + this.testControllerEventsEmitters?.authClicked.fire({ + tabID: msg.tabID, + authType: msg.authType, + }) + } + + private startTestGen(msg: UIMessage) { + this.testControllerEventsEmitters?.startTestGen.fire({ + tabID: msg.tabID, + prompt: msg.prompt, + }) + } + + // Takes user input from chat input box. + private processChatPrompt(msg: UIMessage) { + this.testControllerEventsEmitters?.processHumanChatMessage.fire({ + prompt: msg.chatMessage, + tabID: msg.tabID, + }) + } + + private formActionClicked(msg: UIMessage) { + this.testControllerEventsEmitters?.formActionClicked.fire({ + ...msg, + }) + } + + private followUpClicked(msg: any) { + this.testControllerEventsEmitters?.followUpClicked.fire({ + followUp: msg.followUp, + tabID: msg.tabID, + }) + } + + private openDiff(msg: any) { + this.testControllerEventsEmitters?.openDiff.fire({ + tabID: msg.tabID, + filePath: msg.filePath, + deleted: msg.deleted, + messageId: msg.messageId, + }) + } + + private insertCodeAtCursorPosition(msg: any) { + this.testControllerEventsEmitters?.insertCodeAtCursorPosition.fire({ + command: msg.command, + messageId: msg.messageId, + tabID: msg.tabID, + code: msg.code, + insertionTargetType: msg.insertionTargetType, + codeReference: msg.codeReference, + }) + } + + private processResponseBodyLinkClick(msg: UIMessage) { + this.testControllerEventsEmitters?.processResponseBodyLinkClick.fire({ + command: msg.command, + messageId: msg.messageId, + tabID: msg.tabID, + link: msg.link, + }) + } +} diff --git a/packages/core/src/amazonqTest/chat/views/connector/connector.ts b/packages/core/src/amazonqTest/chat/views/connector/connector.ts new file mode 100644 index 00000000000..bc0c61c5390 --- /dev/null +++ b/packages/core/src/amazonqTest/chat/views/connector/connector.ts @@ -0,0 +1,254 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AuthFollowUpType } from '../../../../amazonq/auth/model' +import { MessagePublisher } from '../../../../amazonq/messages/messagePublisher' +import { ChatItemAction, ChatItemButton, ProgressField, ChatItemContent } from '@aws/mynah-ui/dist/static' +import { ChatItemType } from '../../../../amazonq/commons/model' +import { testChat } from '../../../models/constants' +import { MynahIcons } from '@aws/mynah-ui' +import { SendBuildProgressMessageParams } from '../../controller/messenger/messenger' +import { CodeReference } from '../../../../codewhispererChat/view/connector/connector' + +class UiMessage { + readonly time: number = Date.now() + readonly sender: string = testChat + readonly type: TestMessageType = 'chatMessage' + readonly status: string = 'info' + + public constructor(protected tabID: string) {} +} + +export type TestMessageType = + | 'authenticationUpdateMessage' + | 'authNeededException' + | 'chatMessage' + | 'chatInputEnabledMessage' + | 'updatePlaceholderMessage' + | 'errorMessage' + | 'updatePromptProgress' + | 'chatSummaryMessage' + | 'buildProgressMessage' + +export class AuthenticationUpdateMessage { + readonly time: number = Date.now() + readonly sender: string = testChat + readonly type: TestMessageType = 'authenticationUpdateMessage' + + constructor( + readonly testEnabled: boolean, + readonly authenticatingTabIDs: string[] + ) {} +} + +export class UpdatePromptProgressMessage extends UiMessage { + readonly progressField: ProgressField | null + override type: TestMessageType = 'updatePromptProgress' + constructor(tabID: string, progressField: ProgressField | null) { + super(tabID) + this.progressField = progressField + } +} + +export class AuthNeededException extends UiMessage { + override type: TestMessageType = 'authNeededException' + + constructor( + readonly message: string, + readonly authType: AuthFollowUpType, + tabID: string + ) { + super(tabID) + } +} + +export interface ChatMessageProps { + readonly message: string | undefined + readonly messageId?: string | undefined + readonly messageType: ChatItemType + readonly buttons?: ChatItemButton[] + readonly followUps?: ChatItemAction[] + readonly canBeVoted?: boolean + readonly filePath?: string + readonly informationCard?: ChatItemContent['informationCard'] +} + +export class ChatMessage extends UiMessage { + readonly message: string | undefined + readonly messageId?: string | undefined + readonly messageType: ChatItemType + readonly canBeVoted?: boolean + readonly informationCard: ChatItemContent['informationCard'] + override type: TestMessageType = 'chatMessage' + + constructor(props: ChatMessageProps, tabID: string) { + super(tabID) + this.message = props.message + this.messageType = props.messageType + this.messageId = props.messageId || undefined + this.canBeVoted = props.canBeVoted || undefined + this.informationCard = props.informationCard || undefined + } +} + +export class ChatSummaryMessage extends UiMessage { + readonly message: string | undefined + readonly messageId?: string | undefined + readonly messageType: ChatItemType + readonly buttons: ChatItemButton[] + readonly canBeVoted?: boolean + readonly filePath?: string + override type: TestMessageType = 'chatSummaryMessage' + + constructor(props: ChatMessageProps, tabID: string) { + super(tabID) + this.message = props.message + this.messageType = props.messageType + this.buttons = props.buttons || [] + this.messageId = props.messageId || undefined + this.canBeVoted = props.canBeVoted + this.filePath = props.filePath + } +} + +export class ChatInputEnabledMessage extends UiMessage { + override type: TestMessageType = 'chatInputEnabledMessage' + + constructor( + tabID: string, + readonly enabled: boolean + ) { + super(tabID) + } +} + +export class UpdatePlaceholderMessage extends UiMessage { + readonly newPlaceholder: string + override type: TestMessageType = 'updatePlaceholderMessage' + + constructor(tabID: string, newPlaceholder: string) { + super(tabID) + this.newPlaceholder = newPlaceholder + } +} + +export class CapabilityCardMessage extends ChatMessage { + constructor(tabID: string) { + super( + { + message: '', + messageType: 'answer', + informationCard: { + title: '/test', + description: 'Included in your Q Developer Agent subscription', + content: { + body: `I can generate unit tests for your active file. + +After you select the functions or methods I should focus on, I will: +1. Generate unit tests +2. Place them into relevant test file + +To learn more, check out our [User Guide](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-in-IDE.html).`, + }, + icon: 'check-list' as MynahIcons, + }, + }, + tabID + ) + } +} + +export class ErrorMessage extends UiMessage { + readonly title!: string + readonly message!: string + override type: TestMessageType = 'errorMessage' + + constructor(title: string, message: string, tabID: string) { + super(tabID) + this.title = title + this.message = message + } +} + +export class BuildProgressMessage extends UiMessage { + readonly message: string | undefined + readonly codeGenerationId!: string + readonly messageId?: string + readonly followUps?: { + text?: string + options?: ChatItemAction[] + } + readonly fileList?: { + fileTreeTitle?: string + rootFolderTitle?: string + filePaths?: string[] + } + readonly codeReference?: CodeReference[] + readonly canBeVoted: boolean + readonly messageType: ChatItemType + override type: TestMessageType = 'buildProgressMessage' + + constructor({ + tabID, + messageType, + codeGenerationId, + message, + canBeVoted, + messageId, + followUps, + fileList, + codeReference, + }: SendBuildProgressMessageParams) { + super(tabID) + this.messageType = messageType + this.codeGenerationId = codeGenerationId + this.message = message + this.canBeVoted = canBeVoted + this.messageId = messageId + this.followUps = followUps + this.fileList = fileList + this.codeReference = codeReference + } +} + +export class AppToWebViewMessageDispatcher { + constructor(private readonly appsToWebViewMessagePublisher: MessagePublisher) {} + + public sendChatMessage(message: ChatMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendChatSummaryMessage(message: ChatSummaryMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendUpdatePlaceholder(message: UpdatePlaceholderMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendAuthenticationUpdate(message: AuthenticationUpdateMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendAuthNeededExceptionMessage(message: AuthNeededException) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendChatInputEnabled(message: ChatInputEnabledMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendErrorMessage(message: ErrorMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendBuildProgressMessage(message: BuildProgressMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } + + public sendUpdatePromptProgress(message: UpdatePromptProgressMessage) { + this.appsToWebViewMessagePublisher.publish(message) + } +} diff --git a/packages/core/src/amazonqTest/index.ts b/packages/core/src/amazonqTest/index.ts new file mode 100644 index 00000000000..06f5ebb63f9 --- /dev/null +++ b/packages/core/src/amazonqTest/index.ts @@ -0,0 +1,6 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export { default as MessengerUtils } from './chat/controller/messenger/messengerUtils' diff --git a/packages/core/src/amazonqTest/models/constants.ts b/packages/core/src/amazonqTest/models/constants.ts new file mode 100644 index 00000000000..8370d4d3ca7 --- /dev/null +++ b/packages/core/src/amazonqTest/models/constants.ts @@ -0,0 +1,145 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { ProgressField, MynahIcons, ChatItemButton } from '@aws/mynah-ui' +import { ButtonActions } from '../chat/controller/messenger/messengerUtils' +import { TestGenerationBuildStep } from '../../codewhisperer' +import { ChatSessionManager } from '../chat/storages/chatSession' +import { BuildStatus } from '../chat/session/session' + +// For uniquely identifiying which chat messages should be routed to Test +export const testChat = 'testChat' + +export const cancelTestGenButton: ChatItemButton = { + id: ButtonActions.STOP_TEST_GEN, + text: 'Cancel', + icon: 'cancel' as MynahIcons, +} + +export const testGenProgressField: ProgressField = { + status: 'default', + value: -1, + text: 'Generating unit tests...', + actions: [cancelTestGenButton], +} + +export const testGenCompletedField: ProgressField = { + status: 'success', + value: 100, + text: 'Complete...', + actions: [], +} + +export const cancellingProgressField: ProgressField = { + status: 'warning', + text: 'Cancelling...', + value: -1, + actions: [], +} + +export const cancelBuildProgressButton: ChatItemButton = { + id: ButtonActions.STOP_BUILD, + text: 'Cancel', + icon: 'cancel' as MynahIcons, +} + +export const buildProgressField: ProgressField = { + status: 'default', + value: -1, + text: 'Executing...', + actions: [cancelBuildProgressButton], +} + +export const errorProgressField: ProgressField = { + status: 'error', + text: 'Error...Input needed', + value: -1, + actions: [cancelBuildProgressButton], +} + +export const testGenSummaryMessage = ( + fileName: string, + planSummary?: string +) => `Sure. This may take a few minutes. I'll share updates here as I work on this. + +**Generating unit tests for the following methods in \`${fileName}\`** +${planSummary ? `\n\n${planSummary}` : ''} +` + +const checkIcons = { + wait: '☐', + current: '☐', + done: '', + error: '❌', +} + +interface StepStatus { + step: TestGenerationBuildStep + status: 'wait' | 'current' | 'done' | 'error' +} + +const stepStatuses: StepStatus[] = [] + +export const testGenBuildProgressMessage = (currentStep: TestGenerationBuildStep, status?: string) => { + const session = ChatSessionManager.Instance.getSession() + const statusText = BuildStatus[session.buildStatus].toLowerCase() + const icon = session.buildStatus === BuildStatus.SUCCESS ? checkIcons['done'] : checkIcons['error'] + let message = `Sure. This may take a few minutes and I'll share updates on my progress here. +**Progress summary**\n\n` + + if (currentStep === TestGenerationBuildStep.START_STEP) { + return message.trim() + } + + updateStepStatuses(currentStep, status) + + if (currentStep >= TestGenerationBuildStep.RUN_BUILD) { + message += `${getIconForStep(TestGenerationBuildStep.RUN_BUILD)} Started build execution\n` + } + + if (currentStep >= TestGenerationBuildStep.RUN_EXECUTION_TESTS) { + message += `${getIconForStep(TestGenerationBuildStep.RUN_EXECUTION_TESTS)} Executing tests\n` + } + + if (currentStep >= TestGenerationBuildStep.FIXING_TEST_CASES && session.buildStatus === BuildStatus.FAILURE) { + message += `${getIconForStep(TestGenerationBuildStep.FIXING_TEST_CASES)} Fixing errors in tests\n\n` + } + + if (currentStep > TestGenerationBuildStep.PROCESS_TEST_RESULTS) { + message += `**Test case summary** +${session.shortAnswer?.testCoverage ? `- Unit test coverage ${session.shortAnswer?.testCoverage}%` : ``} +${icon} Build ${statusText} +${icon} Assertion ${statusText}` + // TODO: Update Assertion % + } + + return message.trim() +} +// TODO: Work on UX to show the build error in the progress message +const updateStepStatuses = (currentStep: TestGenerationBuildStep, status?: string) => { + for (let step = TestGenerationBuildStep.INSTALL_DEPENDENCIES; step <= currentStep; step++) { + const stepStatus: StepStatus = { + step: step, + status: 'wait', + } + + if (step === currentStep) { + stepStatus.status = status === 'failed' ? 'error' : 'current' + } else if (step < currentStep) { + stepStatus.status = 'done' + } + + const existingIndex = stepStatuses.findIndex((s) => s.step === step) + if (existingIndex !== -1) { + stepStatuses[existingIndex] = stepStatus + } else { + stepStatuses.push(stepStatus) + } + } +} + +const getIconForStep = (step: TestGenerationBuildStep) => { + const stepStatus = stepStatuses.find((s) => s.step === step) + return stepStatus ? checkIcons[stepStatus.status] : checkIcons.wait +} diff --git a/packages/core/src/applicationcomposer/activation.ts b/packages/core/src/applicationcomposer/activation.ts index e75266e1ce1..28af3f47d0e 100644 --- a/packages/core/src/applicationcomposer/activation.ts +++ b/packages/core/src/applicationcomposer/activation.ts @@ -10,7 +10,7 @@ import { openTemplateInComposerCommand } from './commands/openTemplateInComposer import { openInComposerDialogCommand } from './commands/openInComposerDialog' export async function activate(extensionContext: vscode.ExtensionContext): Promise { - const visualizationManager = new ApplicationComposerManager(extensionContext) + const visualizationManager = await ApplicationComposerManager.create(extensionContext) extensionContext.subscriptions.push( openTemplateInComposerCommand.register(visualizationManager), diff --git a/packages/core/src/applicationcomposer/codeLensProvider.ts b/packages/core/src/applicationcomposer/codeLensProvider.ts index 860459c4c66..844f6ddb9eb 100644 --- a/packages/core/src/applicationcomposer/codeLensProvider.ts +++ b/packages/core/src/applicationcomposer/codeLensProvider.ts @@ -33,7 +33,7 @@ export class ApplicationComposerCodeLensProvider implements vscode.CodeLensProvi } const resourcesLoc = new vscode.Range(codeLensLine, 0, codeLensLine, 0) const codeLens = openTemplateInComposerCommand.build().asCodeLens(resourcesLoc, { - title: localize('AWS.applicationComposer.codeLens.title', 'Visualize with Application Composer'), + title: localize('AWS.applicationComposer.codeLens.title', 'Visualize with Infrastructure Composer'), tooltip: localize( 'AWS.applicationComposer.codeLens.tooltip', 'Visually design and build modern applications quickly' diff --git a/packages/core/src/applicationcomposer/commands/openTemplateInComposer.ts b/packages/core/src/applicationcomposer/commands/openTemplateInComposer.ts index ed50e4ee2da..351673754c2 100644 --- a/packages/core/src/applicationcomposer/commands/openTemplateInComposer.ts +++ b/packages/core/src/applicationcomposer/commands/openTemplateInComposer.ts @@ -8,11 +8,13 @@ import { ApplicationComposerManager } from '../webviewManager' import vscode from 'vscode' import { telemetry } from '../../shared/telemetry/telemetry' import { ToolkitError } from '../../shared/errors' +import { isTreeNode, TreeNode } from '../../shared/treeview/resourceTreeDataProvider' +import { SamAppLocation } from '../../awsService/appBuilder/explorer/samProject' import { getAmazonqApi } from '../../amazonq/extApi' export const openTemplateInComposerCommand = Commands.declare( 'aws.openInApplicationComposer', - (manager: ApplicationComposerManager) => async (arg?: vscode.TextEditor | vscode.Uri) => { + (manager: ApplicationComposerManager) => async (arg?: vscode.TextEditor | vscode.Uri | TreeNode) => { let result: vscode.WebviewPanel | undefined await telemetry.appcomposer_openTemplate.run(async (span) => { const amazonqApi = await getAmazonqApi() @@ -26,8 +28,14 @@ export const openTemplateInComposerCommand = Commands.declare( span.record({ hasChatAuth, }) - arg ??= vscode.window.activeTextEditor - const input = arg instanceof vscode.Uri ? arg : arg?.document + let input = undefined + if (arg instanceof vscode.Uri) { + input = arg + } else if (isTreeNode(arg)) { + input = ((arg as TreeNode).resource as SamAppLocation).samTemplateUri + } else { + input = vscode.window.activeTextEditor?.document + } if (!input) { throw new ToolkitError('No active text editor or document found') diff --git a/packages/core/src/applicationcomposer/composerWebview.ts b/packages/core/src/applicationcomposer/composerWebview.ts index 8e8e6343d06..94493091c3a 100644 --- a/packages/core/src/applicationcomposer/composerWebview.ts +++ b/packages/core/src/applicationcomposer/composerWebview.ts @@ -5,37 +5,43 @@ import { join } from 'path' import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() import * as path from 'path' import * as vscode from 'vscode' import { handleMessage } from './handleMessage' import { FileWatchInfo } from './types' +const localize = nls.loadMessageBundle() + export class ApplicationComposer { public readonly documentUri: vscode.Uri - public webviewPanel: vscode.WebviewPanel + public webviewPanel: vscode.WebviewPanel = {} as vscode.WebviewPanel protected readonly disposables: vscode.Disposable[] = [] protected isPanelDisposed = false private readonly onVisualizationDisposeEmitter = new vscode.EventEmitter() public workSpacePath: string public defaultTemplatePath: string public defaultTemplateName: string - // fileWatches is used to monitor template file changes and achieve bi-direction sync - public fileWatches: Record - private getWebviewContent: () => string - public constructor( + private constructor( textDocument: vscode.TextDocument, - context: vscode.ExtensionContext, - getWebviewContent: () => string + private getWebviewContent: () => Promise, + public fileWatches: Record = {} // fileWatches is used to monitor template file changes and achieve bi-direction sync ) { this.getWebviewContent = getWebviewContent this.documentUri = textDocument.uri - this.webviewPanel = this.setupWebviewPanel(textDocument, context) this.workSpacePath = path.dirname(textDocument.uri.fsPath) this.defaultTemplatePath = textDocument.uri.fsPath this.defaultTemplateName = path.basename(this.defaultTemplatePath) - this.fileWatches = {} + } + + public static async create( + textDocument: vscode.TextDocument, + context: vscode.ExtensionContext, + getWebviewContent: () => Promise + ) { + const obj = new ApplicationComposer(textDocument, getWebviewContent) + obj.webviewPanel = await obj.setupWebviewPanel(textDocument, context) + return obj } public get onVisualizationDisposeEvent(): vscode.Event { @@ -56,7 +62,7 @@ export class ApplicationComposer { if (!this.isPanelDisposed) { this.webviewPanel.dispose() const document = await vscode.workspace.openTextDocument(this.documentUri) - this.webviewPanel = this.setupWebviewPanel(document, context) + this.webviewPanel = await this.setupWebviewPanel(document, context) } } @@ -64,17 +70,17 @@ export class ApplicationComposer { return textDocument.getText() } - private setupWebviewPanel( + private async setupWebviewPanel( textDocument: vscode.TextDocument, context: vscode.ExtensionContext - ): vscode.WebviewPanel { + ): Promise { const documentUri = textDocument.uri // Create and show panel const panel = this.createVisualizationWebviewPanel(documentUri, context) // Set the initial html for the webpage - panel.webview.html = this.getWebviewContent() + panel.webview.html = await this.getWebviewContent() // Handle messages from the webview this.disposables.push( @@ -119,7 +125,11 @@ export class ApplicationComposer { ): vscode.WebviewPanel { const panel = vscode.window.createWebviewPanel( 'applicationComposer', - localize('AWS.applicationComposer.title', '{0} (Application Composer)', path.basename(documentUri.fsPath)), + localize( + 'AWS.applicationComposer.title', + '{0} (Infrastructure Composer)', + path.basename(documentUri.fsPath) + ), { preserveFocus: true, viewColumn: vscode.ViewColumn.Active, diff --git a/packages/core/src/applicationcomposer/messageHandlers/emitTelemetryMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/emitTelemetryMessageHandler.ts index 9ca92ea445b..9b0506b9196 100644 --- a/packages/core/src/applicationcomposer/messageHandlers/emitTelemetryMessageHandler.ts +++ b/packages/core/src/applicationcomposer/messageHandlers/emitTelemetryMessageHandler.ts @@ -67,7 +67,7 @@ export function emitTelemetryMessageHandler(message: EmitTelemetryMessage) { return } } catch (e) { - getLogger().error('Could not log telemetry for App Composer', e) + getLogger().error('Could not log telemetry for App Composer %O', e) } } diff --git a/packages/core/src/applicationcomposer/messageHandlers/generateResourceHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/generateResourceHandler.ts index 1dacff2a93d..6d3e96b81c3 100644 --- a/packages/core/src/applicationcomposer/messageHandlers/generateResourceHandler.ts +++ b/packages/core/src/applicationcomposer/messageHandlers/generateResourceHandler.ts @@ -86,13 +86,13 @@ async function generateResource(cfnType: string) { // TODO-STARLING - Revisit to see if timeout still needed prior to launch const data = await timeout(amazonqApi.chatApi.chat(request), TIMEOUT) const initialResponseTime = globals.clock.Date.now() - startTime - getLogger().debug(`CW Chat initial response: ${JSON.stringify(data, undefined, 2)}, ${initialResponseTime} ms`) + getLogger().debug(`CW Chat initial response: %O, ${initialResponseTime} ms`, data) if (data['$metadata']) { metadata = data['$metadata'] } if (data.generateAssistantResponseResponse === undefined) { - getLogger().debug(`Error: Unexpected model response: ${JSON.stringify(data, undefined, 2)}`) + getLogger().debug(`Error: Unexpected model response: %O`, data) throw new Error('No model response') } @@ -141,12 +141,15 @@ async function generateResource(cfnType: string) { `CW Chat Debug message: cfnType = "${cfnType}", conversationId = ${conversationId}, - metadata = \n${JSON.stringify(metadata, undefined, 2)}, - supplementaryWebLinks = \n${JSON.stringify(supplementaryWebLinks, undefined, 2)}, - references = \n${JSON.stringify(references, undefined, 2)}, + metadata = %O, + supplementaryWebLinks = %O, + references = %O, response = "${response}", initialResponse = ${initialResponseTime} ms, - elapsed time = ${elapsedTime} ms` + elapsed time = ${elapsedTime} ms`, + metadata, + supplementaryWebLinks, + references ) return { @@ -163,7 +166,7 @@ async function generateResource(cfnType: string) { getLogger().debug(`CW Chat error: ${error.name} - ${error.message}`) if (error.$metadata) { const { requestId, cfId, extendedRequestId } = error.$metadata - getLogger().debug(JSON.stringify({ requestId, cfId, extendedRequestId }, undefined, 2)) + getLogger().debug('%O', { requestId, cfId, extendedRequestId }) } throw error diff --git a/packages/core/src/applicationcomposer/messageHandlers/openFeedbackMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/openFeedbackMessageHandler.ts index 7e58d06a6ff..85ddd677c28 100644 --- a/packages/core/src/applicationcomposer/messageHandlers/openFeedbackMessageHandler.ts +++ b/packages/core/src/applicationcomposer/messageHandlers/openFeedbackMessageHandler.ts @@ -7,5 +7,5 @@ import { submitFeedback } from '../../feedback/vue/submitFeedback' import { placeholder } from '../../shared/vscode/commands2' export function openFeedbackMessageHandler() { - void submitFeedback(placeholder, 'Application Composer') + void submitFeedback(placeholder, 'Infrastructure Composer') } diff --git a/packages/core/src/applicationcomposer/webviewManager.ts b/packages/core/src/applicationcomposer/webviewManager.ts index fad4147ad44..8092a2c635f 100644 --- a/packages/core/src/applicationcomposer/webviewManager.ts +++ b/packages/core/src/applicationcomposer/webviewManager.ts @@ -23,13 +23,15 @@ export class ApplicationComposerManager { protected readonly name: string = 'ApplicationComposerManager' protected readonly managedVisualizations = new Map() - protected extensionContext: vscode.ExtensionContext protected webviewHtml?: string protected readonly logger = getLogger() - public constructor(extensionContext: vscode.ExtensionContext) { - this.extensionContext = extensionContext - void this.fetchWebviewHtml() + private constructor(protected extensionContext: vscode.ExtensionContext) {} + + public static async create(extensionContext: vscode.ExtensionContext): Promise { + const obj = new ApplicationComposerManager(extensionContext) + await obj.fetchWebviewHtml() + return obj } private async fetchWebviewHtml() { @@ -41,7 +43,7 @@ export class ApplicationComposerManager { } } - private getWebviewContent = () => { + private getWebviewContent = async () => { if (!this.webviewHtml) { void this.fetchWebviewHtml() return '' @@ -76,7 +78,11 @@ export class ApplicationComposerManager { // Existing visualization does not exist, construct new visualization try { - const newVisualization = new ApplicationComposer(document, this.extensionContext, this.getWebviewContent) + const newVisualization = await ApplicationComposer.create( + document, + this.extensionContext, + this.getWebviewContent + ) this.handleNewVisualization(document.uri.fsPath, newVisualization) if (vscode.version === '1.91.0') { @@ -99,7 +105,11 @@ export class ApplicationComposerManager { const document = await vscode.workspace.openTextDocument({ language: 'yaml', }) - const newVisualization = new ApplicationComposer(document, this.extensionContext, this.getWebviewContent) + const newVisualization = await ApplicationComposer.create( + document, + this.extensionContext, + this.getWebviewContent + ) this.handleNewVisualization(document.uri.fsPath, newVisualization) return newVisualization.getPanel() @@ -116,7 +126,7 @@ export class ApplicationComposerManager { void vscode.window.showInformationMessage( localize( 'AWS.applicationComposer.visualisation.errors.rendering', - 'There was an error rendering Application Composer, check logs for details.' + 'There was an error rendering Infrastructure Composer, check logs for details.' ) ) this.logger.error(`${this.name}: Unable to show App Composer webview: ${err}`) diff --git a/packages/core/src/auth/activation.ts b/packages/core/src/auth/activation.ts index 71b9bd098b8..76c92cb7284 100644 --- a/packages/core/src/auth/activation.ts +++ b/packages/core/src/auth/activation.ts @@ -3,16 +3,28 @@ * SPDX-License-Identifier: Apache-2.0 */ +import * as vscode from 'vscode' import { Auth } from './auth' import { LoginManager } from './deprecated/loginManager' import { fromString } from './providers/credentials' import { getLogger } from '../shared/logger' -import { ExtensionUse } from './utils' -import { isCloud9 } from '../shared/extensionUtilities' +import { ExtensionUse, initializeCredentialsProviderManager } from './utils' +import { isAmazonQ, isCloud9, isSageMaker } from '../shared/extensionUtilities' import { isInDevEnv } from '../shared/vscode/env' import { isWeb } from '../shared/extensionGlobals' +interface SagemakerCookie { + authMode?: 'Sso' | 'Iam' +} + export async function initialize(loginManager: LoginManager): Promise { + if (isAmazonQ() && isSageMaker()) { + // The command `sagemaker.parseCookies` is registered in VS Code Sagemaker environment. + const result = (await vscode.commands.executeCommand('sagemaker.parseCookies')) as SagemakerCookie + if (result.authMode !== 'Sso') { + initializeCredentialsProviderManager() + } + } Auth.instance.onDidChangeActiveConnection(async (conn) => { // This logic needs to be moved to `Auth.useConnection` to correctly record `passive` if (conn?.type === 'iam' && conn.state === 'valid') { diff --git a/packages/core/src/auth/auth.ts b/packages/core/src/auth/auth.ts index d7c2a5d58b7..3335f8ab333 100644 --- a/packages/core/src/auth/auth.ts +++ b/packages/core/src/auth/auth.ts @@ -16,7 +16,7 @@ import { Timeout } from '../shared/utilities/timeoutUtils' import { errorCode, isAwsError, isNetworkError, ToolkitError, UnknownError } from '../shared/errors' import { getCache, getCacheFileWatcher } from './sso/cache' import { isNonNullable, Mutable } from '../shared/utilities/tsUtils' -import { builderIdStartUrl, SsoToken, truncateStartUrl } from './sso/model' +import { SsoToken, truncateStartUrl } from './sso/model' import { SsoClient } from './sso/clients' import { getLogger } from '../shared/logger' import { CredentialsProviderManager } from './providers/credentialsProviderManager' @@ -68,6 +68,7 @@ import { asStringifiedStack } from '../shared/telemetry/spans' import { withTelemetryContext } from '../shared/telemetry/util' import { DiskCacheError } from '../shared/utilities/cacheUtils' import { setContext } from '../shared/vscode/setContext' +import { builderIdStartUrl, internalStartUrl } from './sso/constants' interface AuthService { /** @@ -125,8 +126,6 @@ export interface ConnectionStateChangeEvent { readonly state: ProfileMetadata['connectionState'] } -export type AuthType = Auth - export type DeletedConnection = { connId: Connection['id']; storedProfile?: StoredProfile } type DeclaredConnection = Pick & { source: string } @@ -189,7 +188,7 @@ export class Auth implements AuthService, ConnectionManager { } public isInternalAmazonUser(): boolean { - return this.isConnected() && this.startUrl === 'https://amzn.awsapps.com/start' + return this.isConnected() && this.startUrl === internalStartUrl } /** diff --git a/packages/core/src/auth/connection.ts b/packages/core/src/auth/connection.ts index 29ab6d1b8ee..3e7752dd8e9 100644 --- a/packages/core/src/auth/connection.ts +++ b/packages/core/src/auth/connection.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode' import { Credentials } from '@aws-sdk/types' import { Mutable } from '../shared/utilities/tsUtils' -import { builderIdStartUrl, ClientRegistration, SsoToken, truncateStartUrl } from './sso/model' +import { ClientRegistration, SsoToken, truncateStartUrl } from './sso/model' import { SsoClient } from './sso/clients' import { CredentialsProviderManager } from './providers/credentialsProviderManager' import { fromString } from './providers/credentials' @@ -17,12 +17,14 @@ import { withTelemetryContext } from '../shared/telemetry/util' import { AuthModifyConnection, telemetry } from '../shared/telemetry/telemetry' import { asStringifiedStack } from '../shared/telemetry/spans' import { getTelemetryReason, getTelemetryReasonDesc } from '../shared/errors' +import { builderIdStartUrl } from './sso/constants' /** Shows a warning message unless it is the same as the last one shown. */ const warnOnce = onceChanged((s: string, url: string) => { void showMessageWithUrl(s, url, undefined, 'warn') }) +// TODO: Refactor all scopes to a central file with minimal dependencies. export const scopesCodeCatalyst = ['codecatalyst:read_write'] export const scopesSsoAccountAccess = ['sso:account:access'] /** These are the non-chat scopes for CW. */ @@ -38,6 +40,11 @@ type SsoType = | 'idc' // AWS Identity Center | 'builderId' +// TODO: This type is not centralized and there are many routines in the codebase that use some +// variation for these for validation, telemetry, UX, etc. A refactor is needed to align these +// string types. +export type AuthType = 'credentials' | 'builderId' | 'identityCenter' | 'unknown' + export const isIamConnection = (conn?: Connection): conn is IamConnection => conn?.type === 'iam' export const isSsoConnection = (conn?: Connection, type: SsoType = 'any'): conn is SsoConnection => { if (conn?.type !== 'sso') { @@ -73,7 +80,7 @@ export function hasScopes(target: SsoConnection | SsoProfile | string[], scopes: * Not optimized, but the set of possible scopes is currently very small (< 8) */ export function hasExactScopes(target: SsoConnection | SsoProfile | string[], scopes: string[]): boolean { - const targetScopes = Array.isArray(target) ? target : target.scopes ?? [] + const targetScopes = Array.isArray(target) ? target : (target.scopes ?? []) return scopes.length === targetScopes.length && scopes.every((s) => targetScopes.includes(s)) } diff --git a/packages/core/src/auth/credentials/sharedCredentials.ts b/packages/core/src/auth/credentials/sharedCredentials.ts index 146940a1a9b..1b7ea038653 100644 --- a/packages/core/src/auth/credentials/sharedCredentials.ts +++ b/packages/core/src/auth/credentials/sharedCredentials.ts @@ -230,7 +230,7 @@ async function loadConfigFile(configUri?: vscode.Uri): Promise> { @@ -238,7 +238,7 @@ async function loadCredentialsFile(credentialsUri?: vscode.Uri): Promise { private readonly logger: Logger = getLogger() - private loadedCredentialsModificationMillis?: number - private loadedConfigModificationMillis?: number - public async refresh(): Promise { - if (await this.needsRefresh()) { - await this.loadSharedCredentialsProviders() - } + await this.loadSharedCredentialsProviders() } public override getProviderType(): CredentialsProviderType | undefined { return SharedCredentialsProvider.getProviderType() } - protected override resetProviders() { - this.loadedCredentialsModificationMillis = undefined - this.loadedConfigModificationMillis = undefined - - super.resetProviders() - } - - private async needsRefresh(): Promise { - const credentialsLastModMillis = await this.getLastModifiedMillis(getCredentialsFilename()) - const configLastModMillis = await this.getLastModifiedMillis(getConfigFilename()) - - return ( - this.loadedCredentialsModificationMillis !== credentialsLastModMillis || - this.loadedConfigModificationMillis !== configLastModMillis - ) - } - private async loadSharedCredentialsProviders(): Promise { this.resetProviders() @@ -52,9 +28,6 @@ export class SharedCredentialsProviderFactory extends BaseCredentialsProviderFac const errors = result.errors.map((e) => e.message).join('\t\n') getLogger().warn(`credentials: errors while parsing:\n%s`, errors) } - - this.loadedCredentialsModificationMillis = await this.getLastModifiedMillis(getCredentialsFilename()) - this.loadedConfigModificationMillis = await this.getLastModifiedMillis(getConfigFilename()) await updateAwsSdkLoadConfigEnvVar() getLogger().verbose( @@ -79,13 +52,4 @@ export class SharedCredentialsProviderFactory extends BaseCredentialsProviderFac this.addProvider(provider) } } - - private async getLastModifiedMillis(filepath: string): Promise { - try { - const stat = await fs.stat(filepath) - return stat.mtime - } catch (err) { - return undefined - } - } } diff --git a/packages/core/src/auth/secondaryAuth.ts b/packages/core/src/auth/secondaryAuth.ts index e52236987df..add91765833 100644 --- a/packages/core/src/auth/secondaryAuth.ts +++ b/packages/core/src/auth/secondaryAuth.ts @@ -12,7 +12,7 @@ import { isNonNullable } from '../shared/utilities/tsUtils' import { ToolIdStateKey } from '../shared/globalState' import { Connection, getTelemetryMetadataForConn, SsoConnection, StatefulConnection } from './connection' import { indent } from '../shared/utilities/textUtilities' -import { AuthStatus, telemetry } from '../shared/telemetry/telemetry' +import { AuthModifyConnection, AuthStatus, Span, telemetry } from '../shared/telemetry/telemetry' import { asStringifiedStack } from '../shared/telemetry/spans' import { withTelemetryContext } from '../shared/telemetry/util' import { isNetworkError } from '../shared/errors' @@ -307,7 +307,7 @@ export class SecondaryAuth { connectionState: 'undefined', }) await this.auth.tryAutoConnect() - this.#savedConnection = await this._loadSavedConnection() + this.#savedConnection = await this._loadSavedConnection(span) this.#onDidChangeActiveConnection.fire(this.activeConnection) const conn = this.#savedConnection @@ -328,7 +328,7 @@ export class SecondaryAuth { /** * Provides telemetry if called by restoreConnection() (or another auth_modifyConnection context) */ - private async _loadSavedConnection() { + private async _loadSavedConnection(span: Span) { const id = this.getStateConnectionId() if (id === undefined) { return @@ -349,7 +349,7 @@ export class SecondaryAuth { let connectionState = this.auth.getConnectionState(conn) // This function is expected to be called in the context of restoreConnection() - telemetry.auth_modifyConnection.record({ + span.record({ connectionState, authStatus: getAuthStatus(connectionState), }) @@ -358,7 +358,7 @@ export class SecondaryAuth { await this.auth.refreshConnectionState(conn) connectionState = this.auth.getConnectionState(conn) - telemetry.auth_modifyConnection.record({ + span.record({ connectionState, authStatus: getAuthStatus(connectionState), }) @@ -367,7 +367,7 @@ export class SecondaryAuth { // If updating the state fails, then we should delegate downstream to handle getting the proper state. getLogger().error('loadSavedConnection: Failed to refresh connection state: %s', err) if (isNetworkError(err) && connectionState === 'valid') { - telemetry.auth_modifyConnection.record({ + span.record({ authStatus: 'connectedWithNetworkError', }) } diff --git a/packages/core/src/auth/sso/cache.ts b/packages/core/src/auth/sso/cache.ts index 1c2be980630..a9cdfa43ce0 100644 --- a/packages/core/src/auth/sso/cache.ts +++ b/packages/core/src/auth/sso/cache.ts @@ -15,6 +15,7 @@ import { SsoToken, ClientRegistration } from './model' import { DevSettings } from '../../shared/settings' import { onceChanged } from '../../shared/utilities/functionUtils' import globals from '../../shared/extensionGlobals' +import { ToolkitError } from '../../shared/errors' interface RegistrationKey { readonly startUrl: string @@ -78,6 +79,11 @@ export function getTokenCache(directory = getCacheDir()): KeyedCache } function read(data: StoredToken): SsoAccess { + // Validate data is not missing. Since the input data is passed directly from whatever is on disk. + if (!hasProps(data, 'accessToken')) { + throw new ToolkitError(`SSO cache data looks malformed`) + } + const registration = hasProps(data, 'clientId', 'clientSecret', 'registrationExpiresAt') ? { ...selectFrom(data, 'clientId', 'clientSecret', 'scopes', 'startUrl'), diff --git a/packages/core/src/auth/sso/constants.ts b/packages/core/src/auth/sso/constants.ts index 4d9c348cdc1..4b0e781ceaa 100644 --- a/packages/core/src/auth/sso/constants.ts +++ b/packages/core/src/auth/sso/constants.ts @@ -8,6 +8,9 @@ * web, node, or vue. */ +export const builderIdStartUrl = 'https://view.awsapps.com/start' +export const internalStartUrl = 'https://amzn.awsapps.com/start' + export const ssoUrlFormatRegex = /^(https?:\/\/(.+)\.awsapps\.com\/start|https?:\/\/identitycenter\.amazonaws\.com\/ssoins-[\da-zA-Z]{16})\/?$/ diff --git a/packages/core/src/auth/sso/model.ts b/packages/core/src/auth/sso/model.ts index b0c26d5872f..2a05692148e 100644 --- a/packages/core/src/auth/sso/model.ts +++ b/packages/core/src/auth/sso/model.ts @@ -16,6 +16,7 @@ import { ssoAuthHelpUrl } from '../../shared/constants' import { openUrl } from '../../shared/utilities/vsCodeUtils' import { ToolkitError } from '../../shared/errors' import { isCloud9 } from '../../shared/extensionUtilities' +import { builderIdStartUrl } from './constants' export interface SsoToken { /** @@ -88,7 +89,6 @@ export interface SsoProfile { readonly identifier?: string } -export const builderIdStartUrl = 'https://view.awsapps.com/start' export const trustedDomainCancellation = 'TrustedDomainCancellation' const tryOpenHelpUrl = (url: vscode.Uri) => diff --git a/packages/core/src/auth/sso/server.ts b/packages/core/src/auth/sso/server.ts index c8b21246f67..7c95b5ce016 100644 --- a/packages/core/src/auth/sso/server.ts +++ b/packages/core/src/auth/sso/server.ts @@ -218,7 +218,7 @@ export class AuthSSOServer { private async loadResource(res: http.ServerResponse, resourcePath: string) { try { - const file = await fs.readFile(resourcePath) + const file = await fs.readFileBytes(resourcePath) res.writeHead(200) res.end(file) } catch (e) { diff --git a/packages/core/src/auth/sso/ssoAccessTokenProvider.ts b/packages/core/src/auth/sso/ssoAccessTokenProvider.ts index af00ea4f3b2..6639e9f3832 100644 --- a/packages/core/src/auth/sso/ssoAccessTokenProvider.ts +++ b/packages/core/src/auth/sso/ssoAccessTokenProvider.ts @@ -6,15 +6,7 @@ import * as vscode from 'vscode' import globals from '../../shared/extensionGlobals' import { AuthorizationPendingException, SSOOIDCServiceException, SlowDownException } from '@aws-sdk/client-sso-oidc' -import { - SsoToken, - ClientRegistration, - isExpired, - SsoProfile, - builderIdStartUrl, - openSsoPortalLink, - isDeprecatedAuth, -} from './model' +import { SsoToken, ClientRegistration, isExpired, SsoProfile, openSsoPortalLink, isDeprecatedAuth } from './model' import { getCache } from './cache' import { hasProps, hasStringProps, RequiredProps, selectFrom } from '../../shared/utilities/tsUtils' import { OidcClient } from './clients' @@ -38,7 +30,7 @@ import { getIdeProperties, isAmazonQ, isCloud9 } from '../../shared/extensionUti import { randomBytes, createHash } from 'crypto' import { localize } from '../../shared/utilities/vsCodeUtils' import { randomUUID } from '../../shared/crypto' -import { isRemoteWorkspace, isWebWorkspace } from '../../shared/vscode/env' +import { getExtRuntimeContext } from '../../shared/vscode/env' import { showInputBox } from '../../shared/ui/inputPrompter' import { AmazonQPromptSettings, DevSettings, PromptSettings, ToolkitPromptSettings } from '../../shared/settings' import { onceChanged } from '../../shared/utilities/functionUtils' @@ -46,6 +38,7 @@ import { NestedMap } from '../../shared/utilities/map' import { asStringifiedStack } from '../../shared/telemetry/spans' import { showViewLogsMessage } from '../../shared/utilities/messages' import _ from 'lodash' +import { builderIdStartUrl } from './constants' export const authenticationPath = 'sso/authenticated' @@ -263,6 +256,7 @@ export abstract class SsoAccessTokenProvider { awsRegion: this.profile.region, ssoRegistrationExpiresAt: args?.registrationExpiresAt, ssoRegistrationClientId: args?.registrationClientId, + sessionDuration: getSessionDuration(this.tokenCacheKey), }) // Reset source in case there is a case where browser login was called but we forgot to set the source. @@ -304,10 +298,10 @@ export abstract class SsoAccessTokenProvider { * * Since we are unable to serve the final authorization page */ - return isRemoteWorkspace() || isWebWorkspace() + return getExtRuntimeContext().extensionHost === 'remote' } ) { - if (DevSettings.instance.get('webAuth', false) && isWebWorkspace()) { + if (DevSettings.instance.get('webAuth', false) && getExtRuntimeContext().extensionHost === 'webworker') { return new WebAuthorization(profile, cache, oidc, reAuthState) } if (useDeviceFlow()) { @@ -403,7 +397,7 @@ async function pollForTokenWithProgress( */ function getSessionDuration(id: string) { const creationDate = globals.globalState.getSsoSessionCreationDate(id) - return creationDate !== undefined ? Date.now() - creationDate : undefined + return creationDate !== undefined ? globals.clock.Date.now() - creationDate : undefined } /** @@ -814,7 +808,7 @@ class DiskCacheErrorMessage { : ToolkitPromptSettings.instance // We know 'ssoCacheError' is in all extension prompt settings - if (await promptSettings.isPromptEnabled('ssoCacheError')) { + if (promptSettings.isPromptEnabled('ssoCacheError')) { const result = await showMessage() if (result === dontShow) { await promptSettings.disablePrompt('ssoCacheError') diff --git a/packages/core/src/auth/sso/validation.ts b/packages/core/src/auth/sso/validation.ts index ecfe935a837..cad6aa7f4c3 100644 --- a/packages/core/src/auth/sso/validation.ts +++ b/packages/core/src/auth/sso/validation.ts @@ -4,7 +4,7 @@ */ import * as vscode from 'vscode' import { UnknownError } from '../../shared/errors' -import { AuthType } from '../auth' +import { Auth } from '../auth' import { SsoConnection, hasScopes, isAnySsoConnection } from '../connection' import { ssoUrlFormatMessage, ssoUrlFormatRegex } from './constants' @@ -19,7 +19,7 @@ export function validateSsoUrlFormat(url: string) { } export async function validateIsNewSsoUrlAsync( - auth: AuthType, + auth: Auth, url: string, requiredScopes?: string[] ): Promise { diff --git a/packages/core/src/auth/utils.ts b/packages/core/src/auth/utils.ts index ece410203b9..d160ce4b490 100644 --- a/packages/core/src/auth/utils.ts +++ b/packages/core/src/auth/utils.ts @@ -18,7 +18,7 @@ import { formatError, ToolkitError } from '../shared/errors' import { asString } from './providers/credentials' import { TreeNode } from '../shared/treeview/resourceTreeDataProvider' import { createInputBox } from '../shared/ui/inputPrompter' -import { telemetry } from '../shared/telemetry/telemetry' +import { CredentialSourceId, telemetry } from '../shared/telemetry/telemetry' import { createCommonButtons, createExitButton, createHelpButton, createRefreshButton } from '../shared/ui/buttons' import { getIdeProperties, isAmazonQ, isCloud9 } from '../shared/extensionUtilities' import { addScopes, getDependentAuths } from './secondaryAuth' @@ -45,13 +45,21 @@ import { Commands, placeholder } from '../shared/vscode/commands2' import { Auth } from './auth' import { validateIsNewSsoUrl, validateSsoUrlFormat } from './sso/validation' import { getLogger } from '../shared/logger' -import { isValidAmazonQConnection, isValidCodeWhispererCoreConnection } from '../codewhisperer/util/authUtil' +import { AuthUtil, isValidAmazonQConnection, isValidCodeWhispererCoreConnection } from '../codewhisperer/util/authUtil' import { AuthFormId } from '../login/webview/vue/types' import { extensionVersion } from '../shared/vscode/env' import { ExtStartUpSources } from '../shared/telemetry' import { CommonAuthWebview } from '../login/webview/vue/backend' import { AuthSource } from '../login/webview/util' import { setContext } from '../shared/vscode/setContext' +import { CredentialsProviderManager } from './providers/credentialsProviderManager' +import { SharedCredentialsProviderFactory } from './providers/sharedCredentialsProviderFactory' +import { Ec2CredentialsProvider } from './providers/ec2CredentialsProvider' +import { EcsCredentialsProvider } from './providers/ecsCredentialsProvider' +import { EnvVarsCredentialsProvider } from './providers/envVarsCredentialsProvider' +import { showMessageWithUrl } from '../shared/utilities/messages' +import { credentialHelpUrl } from '../shared/constants' +import { ExtStartUpSource } from '../shared/telemetry/util' // iam-only excludes Builder ID and IAM Identity Center from the list of valid connections // TODO: Understand if "iam" should include these from the list at all @@ -101,6 +109,46 @@ export async function promptAndUseConnection(...[auth, type]: Parameters { return telemetry.function_call.run( async () => { @@ -687,6 +735,19 @@ export class ExtensionUse { return this.wasExtensionUpdated } + /** + * Returns a {@link ExtStartUpSource} based on the current state of the extension. + */ + sourceForTelemetry(): ExtStartUpSource { + if (this.isFirstUse()) { + return ExtStartUpSources.firstStartUp + } else if (this.wasUpdated()) { + return ExtStartUpSources.update + } else { + return ExtStartUpSources.reload + } + } + private updateMemento(key: 'isExtensionFirstUse' | 'lastExtensionVersion', val: any) { globals.globalState.tryUpdate(key, val) } @@ -731,3 +792,19 @@ export function getAuthFormIdsFromConnection(conn?: Connection): AuthFormId[] { return authIds } + +export function initializeCredentialsProviderManager() { + const manager = CredentialsProviderManager.getInstance() + manager.addProviderFactory(new SharedCredentialsProviderFactory()) + manager.addProviders(new Ec2CredentialsProvider(), new EcsCredentialsProvider(), new EnvVarsCredentialsProvider()) +} + +export async function getAuthType() { + let authType: CredentialSourceId | undefined = undefined + if (AuthUtil.instance.isEnterpriseSsoInUse() && AuthUtil.instance.isConnectionValid()) { + authType = 'iamIdentityCenter' + } else if (AuthUtil.instance.isBuilderIdInUse() && AuthUtil.instance.isConnectionValid()) { + authType = 'awsId' + } + return authType +} diff --git a/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.ts b/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.ts index 8e7f60525c1..da9c0738819 100644 --- a/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.ts +++ b/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.ts @@ -4,15 +4,14 @@ */ import * as vscode from 'vscode' -import * as fs from 'fs' +import * as fs from 'fs' // eslint-disable-line no-restricted-imports import * as path from 'path' import { getLogger, Logger } from '../../../shared/logger' import { localize } from '../../../shared/utilities/vsCodeUtils' -import { VueWebview } from '../../../webviews/main' +import { VueWebview, VueWebviewPanel } from '../../../webviews/main' import { ExtContext } from '../../../shared/extensions' import { telemetry } from '../../../shared/telemetry/telemetry' import { AccessAnalyzer, SharedIniFileCredentials } from 'aws-sdk' -import { execFileSync } from 'child_process' import { ToolkitError } from '../../../shared/errors' import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../../shared/filesystemUtilities' import { globals } from '../../../shared' @@ -28,11 +27,12 @@ import { } from './constants' import { DefaultS3Client, parseS3Uri } from '../../../shared/clients/s3Client' import { ExpiredTokenException } from '@aws-sdk/client-sso-oidc' +import { ChildProcess } from '../../../shared/utilities/processUtils' const defaultTerraformConfigPath = 'resources/policychecks-tf-default.yaml' // Diagnostics for Custom checks are shared -const customPolicyCheckDiagnosticCollection = vscode.languages.createDiagnosticCollection('customPolicyCheck') -const validatePolicyDiagnosticCollection = vscode.languages.createDiagnosticCollection('validatePolicy') +export const customPolicyCheckDiagnosticCollection = vscode.languages.createDiagnosticCollection('customPolicyCheck') +export const validatePolicyDiagnosticCollection = vscode.languages.createDiagnosticCollection('validatePolicy') export interface IamPolicyChecksInitialData { checkNoNewAccessFilePath: string @@ -56,7 +56,7 @@ export class IamPolicyChecksWebview extends VueWebview { public static readonly sourcePath: string = 'src/awsService/accessanalyzer/vue/index.js' public readonly id = 'iamPolicyChecks' private static editedDocumentUri: vscode.Uri - private static editedDocumentFileName: string + public static editedDocumentFileName: string private static editedDocument: string public constructor( @@ -119,14 +119,18 @@ export class IamPolicyChecksWebview extends VueWebview { // Send the current active text editor to Webview to show what is being targeted by the user vscode.window.onDidChangeActiveTextEditor((message: any) => { const editedFile = vscode.window.activeTextEditor?.document - IamPolicyChecksWebview.editedDocumentFileName = editedFile!.uri.path - IamPolicyChecksWebview.editedDocument = editedFile!.getText() - IamPolicyChecksWebview.editedDocumentUri = editedFile!.uri - this.onChangeInputPath.fire(editedFile!.uri.path) + if (editedFile !== undefined) { + IamPolicyChecksWebview.editedDocumentFileName = editedFile.uri.path + IamPolicyChecksWebview.editedDocument = editedFile.getText() + IamPolicyChecksWebview.editedDocumentUri = editedFile.uri + this.onChangeInputPath.fire(editedFile.uri.path) + } }) vscode.workspace.onDidChangeTextDocument((message: any) => { const editedFile = vscode.window.activeTextEditor?.document - IamPolicyChecksWebview.editedDocument = editedFile!.getText() + if (editedFile !== undefined) { + IamPolicyChecksWebview.editedDocument = editedFile.getText() + } }) } @@ -273,7 +277,7 @@ export class IamPolicyChecksWebview extends VueWebview { '--config', `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`, ] - this.executeValidatePolicyCommand({ + await this.executeValidatePolicyCommand({ command, args, cfnParameterPathExists: !!cfnParameterPath, @@ -296,7 +300,7 @@ export class IamPolicyChecksWebview extends VueWebview { if (cfnParameterPath !== '') { args.push('--template-configuration-file', `${cfnParameterPath}`) } - this.executeValidatePolicyCommand({ + await this.executeValidatePolicyCommand({ command, args, cfnParameterPathExists: !!cfnParameterPath, @@ -353,7 +357,7 @@ export class IamPolicyChecksWebview extends VueWebview { '--reference-policy-type', `${policyType}`, ] - this.executeCustomPolicyChecksCommand({ + await this.executeCustomPolicyChecksCommand({ command, args, cfnParameterPathExists: !!cfnParameterPath, @@ -387,7 +391,7 @@ export class IamPolicyChecksWebview extends VueWebview { if (cfnParameterPath !== '') { args.push('--template-configuration-file', `${cfnParameterPath}`) } - this.executeCustomPolicyChecksCommand({ + await this.executeCustomPolicyChecksCommand({ command, args, cfnParameterPathExists: !!cfnParameterPath, @@ -450,7 +454,7 @@ export class IamPolicyChecksWebview extends VueWebview { if (resources !== '') { args.push('--resources', `${resources}`) } - this.executeCustomPolicyChecksCommand({ + await this.executeCustomPolicyChecksCommand({ command, args, cfnParameterPathExists: !!cfnParameterPath, @@ -485,7 +489,7 @@ export class IamPolicyChecksWebview extends VueWebview { if (cfnParameterPath !== '') { args.push('--template-configuration-file', `${cfnParameterPath}`) } - this.executeCustomPolicyChecksCommand({ + await this.executeCustomPolicyChecksCommand({ command, args, cfnParameterPathExists: !!cfnParameterPath, @@ -521,7 +525,7 @@ export class IamPolicyChecksWebview extends VueWebview { '--config', `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`, ] - this.executeCustomPolicyChecksCommand({ + await this.executeCustomPolicyChecksCommand({ command, args, cfnParameterPathExists: !!cfnParameterPath, @@ -550,7 +554,7 @@ export class IamPolicyChecksWebview extends VueWebview { if (cfnParameterPath !== '') { args.push('--template-configuration-file', `${cfnParameterPath}`) } - this.executeCustomPolicyChecksCommand({ + await this.executeCustomPolicyChecksCommand({ command, args, cfnParameterPathExists: !!cfnParameterPath, @@ -569,22 +573,22 @@ export class IamPolicyChecksWebview extends VueWebview { } } - public executeValidatePolicyCommand(opts: PolicyCommandOpts & { policyType?: PolicyChecksPolicyType }) { - telemetry.accessanalyzer_iamPolicyChecksValidatePolicy.run((span) => { + public async executeValidatePolicyCommand(opts: PolicyCommandOpts & { policyType?: PolicyChecksPolicyType }) { + await telemetry.accessanalyzer_iamPolicyChecksValidatePolicy.run(async (span) => { try { span.record({ cfnParameterFileUsed: opts.cfnParameterPathExists, documentType: opts.documentType, inputPolicyType: opts.policyType ?? 'None', }) - const resp = execFileSync(opts.command, opts.args) - const findingsCount = this.handleValidatePolicyCliResponse(resp.toString()) + const result = await ChildProcess.run(opts.command, opts.args, { collect: true }) + const findingsCount = this.handleValidatePolicyCliResponse(result.stdout) span.record({ findingsCount: findingsCount, }) } catch (err: any) { if (err.status === 2) { - //CLI responds with a status code of 2 when findings are discovered + // CLI responds with a status code of 2 when findings are discovered const findingsCount = this.handleValidatePolicyCliResponse(err.stdout.toString()) span.record({ findingsCount: findingsCount, @@ -629,10 +633,10 @@ export class IamPolicyChecksWebview extends VueWebview { return findingsCount } - public executeCustomPolicyChecksCommand( + public async executeCustomPolicyChecksCommand( opts: PolicyCommandOpts & { checkType: PolicyChecksCheckType; referencePolicyType?: PolicyChecksPolicyType } ) { - telemetry.accessanalyzer_iamPolicyChecksCustomChecks.run((span) => { + await telemetry.accessanalyzer_iamPolicyChecksCustomChecks.run(async (span) => { try { span.record({ cfnParameterFileUsed: opts.cfnParameterPathExists, @@ -641,14 +645,14 @@ export class IamPolicyChecksWebview extends VueWebview { inputPolicyType: 'None', // Note: This will change once JSON policy language is enabled for Custom policy checks referencePolicyType: opts.referencePolicyType ?? 'None', }) - const resp = execFileSync(opts.command, opts.args) - const findingsCount = this.handleCustomPolicyChecksCliResponse(resp.toString()) + const resp = await ChildProcess.run(opts.command, opts.args) + const findingsCount = this.handleCustomPolicyChecksCliResponse(resp.stdout) span.record({ findingsCount: findingsCount, }) } catch (err: any) { if (err.status === 2) { - //CLI responds with a status code of 2 when findings are discovered + // CLI responds with a status code of 2 when findings are discovered const findingsCount = this.handleCustomPolicyChecksCliResponse(err.stdout.toString()) span.record({ findingsCount: findingsCount, @@ -715,10 +719,10 @@ export class IamPolicyChecksWebview extends VueWebview { } public pushCustomCheckDiagnostic(diagnostics: vscode.Diagnostic[], finding: any, isBlocking: boolean) { - const message = `${finding.findingType}: ${finding.message} - Resource name: ${finding.resourceName}, Policy name: ${finding.policyName}` - if (message.includes('existingPolicyDocument')) { - message.replace('existingPolicyDocument', 'reference document') - } + const findingMessage: string = finding.message.includes('existingPolicyDocument') + ? finding.message.replace('existingPolicyDocument', 'reference document') + : finding.message + const message = `${finding.findingType}: ${findingMessage} - Resource name: ${finding.resourceName}, Policy name: ${finding.policyName}` if (finding.details.reasons) { finding.details.reasons.forEach((reason: any) => { diagnostics.push( @@ -744,11 +748,11 @@ export class IamPolicyChecksWebview extends VueWebview { const Panel = VueWebview.compilePanel(IamPolicyChecksWebview) -export async function renderIamPolicyChecks(context: ExtContext): Promise { +export async function renderIamPolicyChecks(context: ExtContext): Promise { const logger: Logger = getLogger() try { const client = new AccessAnalyzer({ region: context.regionProvider.defaultRegionId }) - //Read from settings to auto-fill some inputs + // Read from settings to auto-fill some inputs const checkNoNewAccessFilePath: string = vscode.workspace .getConfiguration() .get(IamPolicyChecksConstants.CheckNoNewAccessFilePathSetting)! @@ -786,7 +790,7 @@ export async function renderIamPolicyChecks(context: ExtContext): Promise checkAccessNotGrantedResourcesTextArea, customChecksFileErrorMessage, cfnParameterPath: cfnParameterPath ? cfnParameterPath : '', - pythonToolsInstalled: arePythonToolsInstalled(), + pythonToolsInstalled: await arePythonToolsInstalled(), }, client, context.regionProvider.defaultRegionId @@ -795,13 +799,14 @@ export async function renderIamPolicyChecks(context: ExtContext): Promise viewColumn: vscode.ViewColumn.Beside, title: localize('AWS.iamPolicyChecks.title', 'IAM Policy Checks'), }) + return wv } catch (err) { logger.error(err as Error) } } // Helper function to get document contents from a path -async function _readCustomChecksFile(input: string): Promise { +export async function _readCustomChecksFile(input: string): Promise { if (fs.existsSync(input)) { return fs.readFileSync(input).toString() } else { @@ -822,13 +827,13 @@ async function _readCustomChecksFile(input: string): Promise { } } -//Check if Cfn and Tf tools are installed -function arePythonToolsInstalled(): boolean { +// Check if Cfn and Tf tools are installed +export async function arePythonToolsInstalled(): Promise { const logger: Logger = getLogger() let cfnToolInstalled = true let tfToolInstalled = true try { - execFileSync('tf-policy-validator') + await ChildProcess.run('tf-policy-validator') } catch (err: any) { if (isProcessNotFoundErr(err.message)) { tfToolInstalled = false @@ -836,7 +841,7 @@ function arePythonToolsInstalled(): boolean { } } try { - execFileSync('cfn-policy-validator') + await ChildProcess.run('cfn-policy-validator') } catch (err: any) { if (isProcessNotFoundErr(err.message)) { cfnToolInstalled = false @@ -846,12 +851,12 @@ function arePythonToolsInstalled(): boolean { return cfnToolInstalled && tfToolInstalled } -function isProcessNotFoundErr(errMsg: string) { +export function isProcessNotFoundErr(errMsg: string) { return errMsg.includes('command not found') || errMsg.includes('ENOENT') } // Since TypeScript can only get the CLI tool's error output as a string, we have to parse and sanitize it ourselves -function parseCliErrorMessage(message: string, documentType: PolicyChecksDocumentType): string { +export function parseCliErrorMessage(message: string, documentType: PolicyChecksDocumentType): string { const cfnMatch = message.match(/ERROR: .*/) const botoMatch = message.match(/(?<=botocore\.exceptions\.).*/) // Boto errors have a special match const terraformMatch = message.match(/AttributeError:.*/) // Terraform CLI responds with a different error schema... this catches invalid .json plans @@ -876,7 +881,7 @@ function parseCliErrorMessage(message: string, documentType: PolicyChecksDocumen return message } -function getCheckNoNewAccessErrorMessage(finding: any) { +export function getCheckNoNewAccessErrorMessage(finding: any) { if (finding.findingType === 'ERROR') { if ( finding.message.includes( @@ -888,7 +893,7 @@ function getCheckNoNewAccessErrorMessage(finding: any) { } } -function getResultCssColor(resultType: PolicyChecksResult): string { +export function getResultCssColor(resultType: PolicyChecksResult): string { switch (resultType) { case 'Success': return 'var(--vscode-terminal-ansiGreen)' @@ -899,17 +904,17 @@ function getResultCssColor(resultType: PolicyChecksResult): string { } } -function isCloudFormationTemplate(document: string): boolean { +export function isCloudFormationTemplate(document: string): boolean { const cfnFileTypes = ['.yaml', '.yml', '.json'] return cfnFileTypes.some((t) => document.endsWith(t)) } -function isTerraformPlan(document: string) { +export function isTerraformPlan(document: string) { const terraformPlanFileTypes = ['.json'] return terraformPlanFileTypes.some((t) => document.endsWith(t)) } -function isJsonPolicyLanguage(document: string) { +export function isJsonPolicyLanguage(document: string) { const policyLanguageFileTypes = ['.json'] return policyLanguageFileTypes.some((t) => document.endsWith(t)) } diff --git a/packages/core/src/awsService/apigateway/activation.ts b/packages/core/src/awsService/apigateway/activation.ts index 2bf2f44ea4c..78add0d3e67 100644 --- a/packages/core/src/awsService/apigateway/activation.ts +++ b/packages/core/src/awsService/apigateway/activation.ts @@ -9,6 +9,8 @@ import { invokeRemoteRestApi } from './vue/invokeRemoteRestApi' import { copyUrlCommand } from './commands/copyUrl' import { ExtContext } from '../../shared/extensions' import { Commands } from '../../shared/vscode/commands2' +import { TreeNode } from '../../shared/treeview/resourceTreeDataProvider' +import { getSourceNode } from '../../shared/utilities/treeNodeUtils' /** * Activate API Gateway functionality for the extension. @@ -20,14 +22,16 @@ export async function activate(activateArguments: { const extensionContext = activateArguments.extContext.extensionContext const regionProvider = activateArguments.extContext.regionProvider extensionContext.subscriptions.push( - Commands.register('aws.apig.copyUrl', async (node: RestApiNode) => await copyUrlCommand(node, regionProvider)), - Commands.register( - 'aws.apig.invokeRemoteRestApi', - async (node: RestApiNode) => - await invokeRemoteRestApi(activateArguments.extContext, { - apiNode: node, - outputChannel: activateArguments.outputChannel, - }) - ) + Commands.register('aws.apig.copyUrl', async (node: RestApiNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await copyUrlCommand(sourceNode, regionProvider) + }), + Commands.register('aws.apig.invokeRemoteRestApi', async (node: RestApiNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await invokeRemoteRestApi(activateArguments.extContext, { + apiNode: sourceNode, + outputChannel: activateArguments.outputChannel, + }) + }) ) } diff --git a/packages/core/src/awsService/appBuilder/activation.ts b/packages/core/src/awsService/appBuilder/activation.ts new file mode 100644 index 00000000000..9f30282573c --- /dev/null +++ b/packages/core/src/awsService/appBuilder/activation.ts @@ -0,0 +1,205 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import globals from '../../shared/extensionGlobals' +import { ExtContext } from '../../shared/extensions' +import { Commands, VsCodeCommandArg } from '../../shared/vscode/commands2' +import { ToolView } from '../../awsexplorer/toolView' +import { telemetry } from '../../shared/telemetry/telemetry' +import { activateViewsShared, registerToolView } from '../../awsexplorer/activationShared' +import { setContext } from '../../shared/vscode/setContext' +import { fs } from '../../shared/fs/fs' +import { AppBuilderRootNode } from './explorer/nodes/rootNode' +import { initWalkthroughProjectCommand, walkthroughContextString, getOrInstallCliWrapper } from './walkthrough' +import { getLogger } from '../../shared/logger' +import path from 'path' +import { TreeNode } from '../../shared/treeview/resourceTreeDataProvider' +import { runBuild } from '../../shared/sam/build' +import { runOpenHandler, runOpenTemplate } from './utils' +import { ResourceNode } from './explorer/nodes/resourceNode' +import { getSyncWizard, runSync } from '../../shared/sam/sync' +import { getDeployWizard, runDeploy } from '../../shared/sam/deploy' +import { DeployTypeWizard } from './wizards/deployTypeWizard' + +export const templateToOpenAppComposer = 'aws.toolkit.appComposer.templateToOpenOnStart' + +/** + * Activates the AWS Explorer UI and related functionality. + * + * IMPORTANT: Views that should work in all vscode environments (node or web) + * should be setup in {@link activateViewsShared}. + */ +export async function activate(context: ExtContext): Promise { + // recover context variables from global state when activate + const walkthroughSelected = globals.globalState.get(walkthroughContextString) + if (walkthroughSelected !== undefined) { + await setContext(walkthroughContextString, walkthroughSelected) + } + + await registerAppBuilderCommands(context) + + const appBuilderNode: ToolView[] = [ + { + nodes: [AppBuilderRootNode.instance], + view: 'aws.appBuilder', + refreshCommands: [AppBuilderRootNode.instance.refreshAppBuilderExplorer], + }, + { + nodes: [AppBuilderRootNode.instance], + view: 'aws.appBuilderForFileExplorer', + refreshCommands: [AppBuilderRootNode.instance.refreshAppBuilderForFileExplorer], + }, + ] + + const watcher = vscode.workspace.createFileSystemWatcher('**/{template.yaml,template.yml,samconfig.toml}') + watcher.onDidChange(async (uri) => runRefreshAppBuilder(uri, 'changed')) + watcher.onDidCreate(async (uri) => runRefreshAppBuilder(uri, 'created')) + watcher.onDidDelete(async (uri) => runRefreshAppBuilder(uri, 'deleted')) + + for (const viewNode of appBuilderNode) { + registerToolView(viewNode, context.extensionContext) + } + + await openApplicationComposerAfterReload() +} + +async function runRefreshAppBuilder(uri: vscode.Uri, event: string) { + getLogger().debug(`${uri.fsPath} ${event}, refreshing appBuilder`) + await vscode.commands.executeCommand('aws.appBuilderForFileExplorer.refresh') + await vscode.commands.executeCommand('aws.appBuilder.refresh') +} + +/** + * To support open template in AppComposer after extension reload. + * This typically happens when user create project from walkthrough + * and added a new folder to an empty workspace. + * + * Checkes templateToOpenAppComposer in global and opens template + * Directly return if templateToOpenAppComposer is undefined + */ +export async function openApplicationComposerAfterReload(): Promise { + const templatesToOpen = globals.globalState.get<[string]>(templateToOpenAppComposer) + // undefined + if (!templatesToOpen) { + return + } + + for (const template of templatesToOpen) { + const templateUri = vscode.Uri.file(template) + const templateFolder = vscode.Uri.file(path.dirname(template)) + const basename = path.basename(template) + // ignore templates that doesn't belong to current workspace, ignore if not template + if ( + !vscode.workspace.getWorkspaceFolder(templateFolder) || + (basename !== 'template.yaml' && basename !== 'template.yml') + ) { + continue + } + + await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup') + await vscode.commands.executeCommand('aws.openInApplicationComposer', templateUri) + + if (await fs.exists(vscode.Uri.joinPath(templateFolder, 'README.md'))) { + await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup') + await vscode.commands.executeCommand( + 'markdown.showPreview', + vscode.Uri.joinPath(templateFolder, 'README.md') + ) + } + } + // set to undefined + await globals.globalState.update(templateToOpenAppComposer, undefined) +} + +async function setWalkthrough(walkthroughSelected: string = 'S3'): Promise { + await setContext(walkthroughContextString, walkthroughSelected) + await globals.globalState.update(walkthroughContextString, walkthroughSelected) +} + +/** + * + * @param context VScode Context + */ +async function registerAppBuilderCommands(context: ExtContext): Promise { + const source = 'AppBuilderWalkthrough' + context.extensionContext.subscriptions.push( + Commands.register('aws.toolkit.installSAMCLI', async () => { + await getOrInstallCliWrapper('sam-cli', source) + }), + Commands.register('aws.toolkit.installAWSCLI', async () => { + await getOrInstallCliWrapper('aws-cli', source) + }), + Commands.register('aws.toolkit.installDocker', async () => { + await getOrInstallCliWrapper('docker', source) + }), + Commands.register('aws.toolkit.lambda.setWalkthroughToAPI', async () => { + await setWalkthrough('API') + }), + Commands.register('aws.toolkit.lambda.setWalkthroughToS3', async () => { + await setWalkthrough('S3') + }), + Commands.register('aws.toolkit.lambda.setWalkthroughToVisual', async () => { + await setWalkthrough('Visual') + }), + Commands.register('aws.toolkit.lambda.setWalkthroughToCustomTemplate', async () => { + await setWalkthrough('CustomTemplate') + }), + Commands.register('aws.toolkit.lambda.initializeWalkthroughProject', async (): Promise => { + await telemetry.appBuilder_selectWalkthroughTemplate.run(async () => await initWalkthroughProjectCommand()) + await globals.globalState.update('aws.toolkit.lambda.walkthroughCompleted', true) + }), + Commands.register('aws.toolkit.lambda.walkthrough.credential', async (): Promise => { + await vscode.commands.executeCommand('aws.toolkit.auth.manageConnections', source) + }), + Commands.register( + { id: `aws.toolkit.lambda.openWalkthrough`, compositeKey: { 1: 'source' } }, + async (_: VsCodeCommandArg, source?: string) => { + telemetry.appBuilder_startWalkthrough.emit({ source: source }) + await vscode.commands.executeCommand( + 'workbench.action.openWalkthrough', + 'amazonwebservices.aws-toolkit-vscode#aws.toolkit.lambda.walkthrough' + ) + } + ), + Commands.register( + { + id: 'aws.appBuilder.build', + autoconnect: false, + }, + async (arg?: TreeNode | undefined) => await telemetry.sam_build.run(async () => await runBuild(arg)) + ), + Commands.register({ id: 'aws.appBuilder.openTemplate', autoconnect: false }, async (arg: TreeNode) => + telemetry.appBuilder_openTemplate.run(async (span) => { + if (arg) { + span.record({ source: 'AppBuilderOpenTemplate' }) + } else { + span.record({ source: 'commandPalette' }) + } + await runOpenTemplate(arg) + }) + ), + Commands.register({ id: 'aws.appBuilder.openHandler', autoconnect: false }, async (arg: ResourceNode) => + telemetry.lambda_goToHandler.run(async (span) => { + span.record({ source: 'AppBuilderOpenHandler' }) + await runOpenHandler(arg) + }) + ), + Commands.register({ id: 'aws.appBuilder.deploy', autoconnect: true }, async (arg) => { + const wizard = new DeployTypeWizard( + await getSyncWizard('infra', arg, undefined, false), + await getDeployWizard(arg, false) + ) + const choices = await wizard.run() + if (choices) { + if (choices.choice === 'deploy' && choices.deployParam) { + await runDeploy(arg, choices.deployParam) + } else if (choices.choice === 'sync' && choices.syncParam) { + await runSync('infra', arg, undefined, choices.syncParam) + } + } + }) + ) +} diff --git a/packages/core/src/awsService/appBuilder/explorer/detectSamProjects.ts b/packages/core/src/awsService/appBuilder/explorer/detectSamProjects.ts new file mode 100644 index 00000000000..cb179d94f0d --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/detectSamProjects.ts @@ -0,0 +1,61 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { SamAppLocation } from './samProject' +import { getLogger } from '../../../shared/logger/logger' +import { getProjectRootUri } from '../../../shared/sam/utils' + +export async function detectSamProjects(): Promise { + const workspaceFolders = vscode.workspace.workspaceFolders + + if (!workspaceFolders) { + return [] + } + + const results = new Map() + const projects = (await Promise.all(workspaceFolders.map(detectSamProjectsFromWorkspaceFolder))).reduce( + (a, b) => a.concat(b), + [] + ) + + projects.forEach((p) => results.set(p.samTemplateUri.toString(), p)) + + return Array.from(results.values()) +} + +async function detectSamProjectsFromWorkspaceFolder( + workspaceFolder: vscode.WorkspaceFolder +): Promise { + const result: SamAppLocation[] = [] + const samTemplateFiles = await getFiles(workspaceFolder, '**/template.{yml,yaml}', '**/.aws-sam/**') + for (const samTemplateFile of samTemplateFiles) { + const project = { + samTemplateUri: samTemplateFile, + workspaceFolder: workspaceFolder, + projectRoot: getProjectRootUri(samTemplateFile), + } + result.push(project) + } + return result +} + +export async function getFiles( + workspaceFolder: vscode.WorkspaceFolder, + pattern: string, + buildArtifactFolderPattern?: string +): Promise { + try { + const globPattern = new vscode.RelativePattern(workspaceFolder, pattern) + const excludePattern = buildArtifactFolderPattern + ? new vscode.RelativePattern(workspaceFolder, buildArtifactFolderPattern) + : undefined + + return await vscode.workspace.findFiles(globPattern, excludePattern) + } catch (error) { + getLogger().error(`Failed to get files with pattern ${pattern}:`, error) + return [] + } +} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/appNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/appNode.ts new file mode 100644 index 00000000000..5f8c6b4a81e --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/nodes/appNode.ts @@ -0,0 +1,93 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as nls from 'vscode-nls' +const localize = nls.loadMessageBundle() + +import * as vscode from 'vscode' +import { getLogger } from '../../../../shared/logger' +import { ResourceTreeEntity, SamAppLocation, getApp, getStackName } from '../samProject' +import { ResourceNode, generateResourceNodes } from './resourceNode' +import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' +import { createPlaceholderItem } from '../../../../shared/treeview/utils' +import { getIcon } from '../../../../shared/icons' +import { getSamCliContext } from '../../../../shared/sam/cli/samCliContext' +import { SamCliListResourcesParameters } from '../../../../shared/sam/cli/samCliListResources' +import { getDeployedResources, StackResource } from '../../../../lambda/commands/listSamResources' +import * as path from 'path' +import { generateStackNode } from './deployedStack' + +export class AppNode implements TreeNode { + public readonly id = this.location.samTemplateUri.toString() + public readonly resource = this.location + public readonly label = path.join( + this.location.workspaceFolder.name, + path.relative(this.location.workspaceFolder.uri.fsPath, path.dirname(this.location.samTemplateUri.fsPath)) + ) + private stackName: string = '' + public constructor(private readonly location: SamAppLocation) {} + + public async getChildren(): Promise<(ResourceNode | TreeNode)[]> { + const resources = [] + try { + const successfulApp = await getApp(this.location) + const templateResources: ResourceTreeEntity[] = successfulApp.resourceTree + const { stackName, region } = await getStackName(this.location.projectRoot) + this.stackName = stackName + + const listStackResourcesArguments: SamCliListResourcesParameters = { + stackName: this.stackName, + templateFile: this.location.samTemplateUri.fsPath, + region: region, + projectRoot: this.location.projectRoot, + } + + const deployedResources: StackResource[] | undefined = this.stackName + ? await getDeployedResources({ + listResourcesParams: listStackResourcesArguments, + invoker: getSamCliContext().invoker, + }) + : undefined + // Skip generating stack node if stack does not exist in region or other errors + if (deployedResources && deployedResources.length > 0) { + resources.push(...(await generateStackNode(this.stackName, region))) + } + resources.push( + ...generateResourceNodes(this.location, templateResources, this.stackName, region, deployedResources) + ) + + // indicate that App exists, but it is empty + if (resources.length === 0) { + return [ + createPlaceholderItem( + localize('AWS.appBuilder.explorerNode.app.noResource', '[No resource found in SAM template]') + ), + ] + } + return resources + } catch (error) { + getLogger().error(`Could not load the construct tree located at '${this.id}': %O`, error as Error) + return [ + createPlaceholderItem( + localize( + 'AWS.appBuilder.explorerNode.app.noResourceTree', + '[Unable to load Resource tree for this App. Update SAM template]' + ) + ), + ] + } + } + + public getTreeItem() { + const item = new vscode.TreeItem(this.label, vscode.TreeItemCollapsibleState.Collapsed) + + item.contextValue = 'awsAppBuilderAppNode' + item.iconPath = getIcon('vscode-folder') + item.resourceUri = this.location.samTemplateUri + item.tooltip = this.location.samTemplateUri.path + + return item + } +} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/deployedNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/deployedNode.ts new file mode 100644 index 00000000000..913cdd067e0 --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/nodes/deployedNode.ts @@ -0,0 +1,183 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { getIcon } from '../../../../shared/icons' +import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' +import { createPlaceholderItem } from '../../../../shared/treeview/utils' +import * as nls from 'vscode-nls' + +import { getLogger } from '../../../../shared/logger/logger' +import { FunctionConfiguration, LambdaClient, GetFunctionCommand } from '@aws-sdk/client-lambda' +import { DefaultLambdaClient } from '../../../../shared/clients/lambdaClient' +import globals from '../../../../shared/extensionGlobals' +import { defaultPartition } from '../../../../shared/regions/regionProvider' +import { Lambda, APIGateway } from 'aws-sdk' +import { LambdaNode } from '../../../../lambda/explorer/lambdaNodes' +import { LambdaFunctionNode } from '../../../../lambda/explorer/lambdaFunctionNode' +import { DefaultS3Client, DefaultBucket } from '../../../../shared/clients/s3Client' +import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' +import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' +import { ApiGatewayNode } from '../../../../awsService/apigateway/explorer/apiGatewayNodes' +import { RestApiNode } from '../../../../awsService/apigateway/explorer/apiNodes' +import { + SERVERLESS_FUNCTION_TYPE, + SERVERLESS_API_TYPE, + s3BucketType, +} from '../../../../shared/cloudformation/cloudformation' +import { ToolkitError } from '../../../../shared' +import { getIAMConnection } from '../../../../auth/utils' + +const localize = nls.loadMessageBundle() +export interface DeployedResource { + stackName: string + regionCode: string + explorerNode: any + arn: string + contextValue: string +} + +export const DeployedResourceContextValues: Record = { + [SERVERLESS_FUNCTION_TYPE]: 'awsRegionFunctionNodeDownloadable', + [SERVERLESS_API_TYPE]: 'awsApiGatewayNode', + [s3BucketType]: 'awsS3BucketNode', +} + +export class DeployedResourceNode implements TreeNode { + public readonly id: string + public readonly contextValue: string + + public constructor(public readonly resource: DeployedResource) { + if (this.resource.arn) { + this.id = this.resource.arn + this.contextValue = this.resource.contextValue + } else { + getLogger().warn('Cannot create DeployedResourceNode, the ARN does not exist.') + this.id = '' + this.contextValue = '' + } + } + + public async getChildren(): Promise { + return [] + } + + public getTreeItem() { + const item = new vscode.TreeItem(this.id) + + item.contextValue = this.contextValue + item.iconPath = getIcon('vscode-cloud') + item.collapsibleState = vscode.TreeItemCollapsibleState.None + item.tooltip = this.resource.arn + return item + } +} + +export async function generateDeployedNode( + deployedResource: any, + regionCode: string, + stackName: string, + resourceTreeEntity: any +): Promise { + let newDeployedResource: any + const partitionId = globals.regionProvider.getPartitionId(regionCode) ?? defaultPartition + try { + switch (resourceTreeEntity.Type) { + case SERVERLESS_FUNCTION_TYPE: { + const defaultClient = new DefaultLambdaClient(regionCode) + const lambdaNode = new LambdaNode(regionCode, defaultClient) + let configuration: Lambda.FunctionConfiguration + let v3configuration + let logGroupName + try { + configuration = (await defaultClient.getFunction(deployedResource.PhysicalResourceId)) + .Configuration as Lambda.FunctionConfiguration + newDeployedResource = new LambdaFunctionNode(lambdaNode, regionCode, configuration) + } catch (error: any) { + getLogger().error('Error getting Lambda configuration %O', error) + throw ToolkitError.chain(error, 'Error getting Lambda configuration', { + code: 'lambdaClientError', + }) + } + const connection = await getIAMConnection({ prompt: false }) + if (!connection || connection.type !== 'iam') { + return [ + createPlaceholderItem( + localize( + 'AWS.appBuilder.explorerNode.unavailableDeployedResource', + '[Failed to retrive deployed resource.]' + ) + ), + ] + } + const cred = await connection.getCredentials() + const v3Client = new LambdaClient({ region: regionCode, credentials: cred }) + + const v3command = new GetFunctionCommand({ FunctionName: deployedResource.PhysicalResourceId }) + try { + v3configuration = (await v3Client.send(v3command)).Configuration as FunctionConfiguration + logGroupName = v3configuration.LoggingConfig?.LogGroup + } catch { + getLogger().error('Error getting Lambda V3 configuration') + } + newDeployedResource.configuration = { + ...newDeployedResource.configuration, + logGroupName: logGroupName, + } as any + break + } + case s3BucketType: { + const s3Client = new DefaultS3Client(regionCode) + const s3Node = new S3Node(s3Client) + const s3Bucket = new DefaultBucket({ + partitionId: partitionId, + region: regionCode, + name: deployedResource.PhysicalResourceId, + }) + newDeployedResource = new S3BucketNode(s3Bucket, s3Node, s3Client) + break + } + case SERVERLESS_API_TYPE: { + const apiParentNode = new ApiGatewayNode(partitionId, regionCode) + const apiNodes = await apiParentNode.getChildren() + const apiNode = apiNodes.find((node) => node.id === deployedResource.PhysicalResourceId) + newDeployedResource = new RestApiNode( + apiParentNode, + partitionId, + regionCode, + apiNode as APIGateway.RestApi + ) + break + } + default: + newDeployedResource = new DeployedResourceNode(deployedResource) + getLogger().info('Details are missing or are incomplete for: %O', deployedResource) + return [ + createPlaceholderItem( + localize('AWS.appBuilder.explorerNode.noApps', '[This resource is not yet supported.]') + ), + ] + } + } catch (error: any) { + void vscode.window.showErrorMessage(error.messages) + return [ + createPlaceholderItem( + localize( + 'AWS.appBuilder.explorerNode.unavailableDeployedResource', + '[Failed to retrive deployed resource.]' + ) + ), + ] + } + newDeployedResource.contextValue = DeployedResourceContextValues[resourceTreeEntity.Type] + const finalDeployedResource = { + stackName, + regionCode, + explorerNode: newDeployedResource, + arn: newDeployedResource.arn, + contextValue: newDeployedResource.contextValue, + } + return [new DeployedResourceNode(finalDeployedResource)] +} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/deployedStack.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/deployedStack.ts new file mode 100644 index 00000000000..76c8f5ea76b --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/nodes/deployedStack.ts @@ -0,0 +1,66 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' +import { getIcon } from '../../../../shared/icons' +import { CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation' +import { ToolkitError } from '../../../../shared' +import { getIAMConnection } from '../../../../auth/utils' + +export class StackNameNode implements TreeNode { + public readonly id = this.stackName + public readonly resource = this + public arn: string | undefined + public readonly link = `command:aws.explorer.cloudformation.showStack?${encodeURIComponent(JSON.stringify({ stackName: this.stackName, region: this.regionCode }))}` + + public constructor( + public stackName: string, + public regionCode: string + ) { + this.stackName = stackName + this.regionCode = regionCode + } + + public async getChildren(): Promise { + // This stack name node is a leaf node that does not have any children. + return [] + } + public get value(): string { + return `Stack: ${this.stackName} (${this.regionCode})` + } + + public getTreeItem() { + const item = new vscode.TreeItem(this.value) + + item.contextValue = 'awsAppBuilderStackNode' + item.iconPath = getIcon('vscode-cloud') + return item + } +} + +export async function generateStackNode(stackName?: string, regionCode?: string): Promise { + const connection = await getIAMConnection({ prompt: false }) + if (!connection || connection.type !== 'iam') { + return [] + } + const cred = await connection.getCredentials() + const client = new CloudFormationClient({ region: regionCode, credentials: cred }) + try { + const command = new DescribeStacksCommand({ StackName: stackName }) + const response = await client.send(command) + if (response.Stacks && response.Stacks[0]) { + const stackArn = response.Stacks[0].StackId + if (stackName === undefined || regionCode === undefined) { + return [] + } + const stackNode = new StackNameNode(stackName || '', regionCode || '') + stackNode.arn = stackArn + return [stackNode] + } + } catch (error) { + throw new ToolkitError(`Failed to generate stack node ${stackName} in region ${regionCode}: ${error}`) + } + return [] +} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/propertyNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/propertyNode.ts new file mode 100644 index 00000000000..481ecdf7009 --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/nodes/propertyNode.ts @@ -0,0 +1,46 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { getIcon } from '../../../../shared/icons' +import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' + +export class PropertyNode implements TreeNode { + public readonly id = this.key + public readonly resource = this.value + + public constructor( + private readonly key: string, + private readonly value: unknown + ) {} + + public async getChildren(): Promise { + if (this.value instanceof Array || this.value instanceof Object) { + return generatePropertyNodes(this.value) + } else { + return [] + } + } + + public getTreeItem() { + const item = new vscode.TreeItem(`${this.key}: ${this.value}`) + + item.contextValue = 'awsAppBuilderPropertyNode' + item.iconPath = getIcon('vscode-gear') + + if (this.value instanceof Array || this.value instanceof Object) { + item.label = this.key + item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed + } + + return item + } +} + +export function generatePropertyNodes(properties: { [key: string]: any }): TreeNode[] { + return Object.entries(properties) + .filter(([key, _]) => key !== 'Id' && key !== 'Type' && key !== 'Events') + .map(([key, value]) => new PropertyNode(key, value)) +} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/resourceNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/resourceNode.ts new file mode 100644 index 00000000000..bda7b69ac4f --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/nodes/resourceNode.ts @@ -0,0 +1,147 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { IconPath, getIcon } from '../../../../shared/icons' +import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' +import { ResourceTreeEntity, SamAppLocation } from '../samProject' +import { + SERVERLESS_FUNCTION_TYPE, + s3BucketType, + appRunnerType, + ecrRepositoryType, +} from '../../../../shared/cloudformation/cloudformation' +import { generatePropertyNodes } from './propertyNode' +import { generateDeployedNode } from './deployedNode' +import { StackResource } from '../../../../lambda/commands/listSamResources' +import { DeployedResourceNode } from './deployedNode' + +enum ResourceTypeId { + Function = 'function', + Api = 'api', + Other = '', +} + +export class ResourceNode implements TreeNode { + public readonly id = this.resourceTreeEntity.Id + private readonly type = this.resourceTreeEntity.Type + public readonly resourceLogicalId = this.deployedResource?.LogicalResourceId + + public constructor( + private readonly location: SamAppLocation, + private readonly resourceTreeEntity: ResourceTreeEntity, + private readonly stackName?: string, + private readonly region?: string, + private readonly deployedResource?: StackResource, + // TODO: cleanup or rename functionArn parameter as type can be differ from Lambda; value never set in generateResourceNodes() + private readonly functionArn?: string + ) {} + + public get resource() { + return { + resource: this.resourceTreeEntity, + location: this.location.samTemplateUri, + workspaceFolder: this.location.workspaceFolder, + region: this.region, + stackName: this.stackName, + deployedResource: this.deployedResource, + functionArn: this.functionArn, + } + } + + public async getChildren() { + let deployedNodes: DeployedResourceNode[] = [] + let propertyNodes: TreeNode[] = [] + + if (this.deployedResource && this.region && this.stackName) { + deployedNodes = await generateDeployedNode( + this.deployedResource, + this.region, + this.stackName, + this.resourceTreeEntity + ) + } + if (this.resourceTreeEntity.Type === SERVERLESS_FUNCTION_TYPE) { + propertyNodes = generatePropertyNodes(this.resourceTreeEntity) + } + + return [...propertyNodes, ...deployedNodes] + } + + public getTreeItem(): vscode.TreeItem { + // Determine the initial TreeItem collapsible state based on the type + const collapsibleState = this.deployedResource + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None + + // Create the TreeItem with the determined collapsible state + const item = new vscode.TreeItem(this.resourceTreeEntity.Id, collapsibleState) + + // Set the tooltip to the URI of the SAM template + item.tooltip = this.location.samTemplateUri.toString() + + item.iconPath = this.getIconPath() + + // Set the resource URI to the SAM template URI + item.resourceUri = this.location.samTemplateUri + + // Define the context value for the item + item.contextValue = `awsAppBuilderResourceNode.${this.getResourceId()}` + + return item + } + + // Additional resources and corresponding icons will be added in the future. + // When adding support for new resources, ensure that each new resource + // has an appropriate mapping in place. + private getIconPath(): IconPath | undefined { + switch (this.type) { + case SERVERLESS_FUNCTION_TYPE: + return getIcon('aws-lambda-function') + case s3BucketType: + return getIcon('aws-s3-bucket') + case appRunnerType: + return getIcon('aws-apprunner-service') + case ecrRepositoryType: + return getIcon('aws-ecr-registry') + default: + return getIcon('vscode-info') + } + } + + private getResourceId(): ResourceTypeId { + switch (this.type) { + case SERVERLESS_FUNCTION_TYPE: + return ResourceTypeId.Function + case 'Api': + return ResourceTypeId.Api + default: + return ResourceTypeId.Other + } + } +} + +export function generateResourceNodes( + app: SamAppLocation, + resources: NonNullable, + stackName?: string, + region?: string, + deployedResources?: StackResource[] +): ResourceNode[] { + if (!deployedResources) { + return resources.map((resource) => new ResourceNode(app, resource, stackName, region)) + } + + return resources.map((resource) => { + if (resource.Type) { + const deployedResource = deployedResources.find( + (deployedResource) => resource.Id === deployedResource.LogicalResourceId + ) + return new ResourceNode(app, resource, stackName, region, deployedResource) + } else { + return new ResourceNode(app, resource, stackName, region) + } + }) +} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/rootNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/rootNode.ts new file mode 100644 index 00000000000..941ad8ec0f0 --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/nodes/rootNode.ts @@ -0,0 +1,110 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { debugNewSamAppDocUrl } from '../../../../shared/constants' +import { telemetry } from '../../../../shared/telemetry/telemetry' +import { ResourceTreeDataProvider, TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' +import { createPlaceholderItem } from '../../../../shared/treeview/utils' +import { localize, openUrl } from '../../../../shared/utilities/vsCodeUtils' +import { Commands } from '../../../../shared/vscode/commands2' +import { AppNode } from './appNode' +import { detectSamProjects } from '../detectSamProjects' +import globals from '../../../../shared/extensionGlobals' +import { WalkthroughNode } from './walkthroughNode' + +export async function getAppNodes(): Promise { + // no active workspace, show buttons in welcomeview + if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { + return [] + } + + const appsFound = await detectSamProjects() + + if (appsFound.length === 0) { + return [ + createPlaceholderItem( + localize('AWS.appBuilder.explorerNode.noApps', '[No SAM templates found in Workspaces]') + ), + ] + } + + const nodesToReturn: TreeNode[] = appsFound + .map((appLocation) => new AppNode(appLocation)) + .sort((a, b) => a.label.localeCompare(b.label) ?? 0) + + return nodesToReturn +} + +export class AppBuilderRootNode implements TreeNode { + public readonly id = 'appBuilder' + public readonly resource = this + private readonly onDidChangeChildrenEmitter = new vscode.EventEmitter() + public readonly onDidChangeChildren = this.onDidChangeChildrenEmitter.event + private readonly _refreshAppBuilderExplorer + private readonly _refreshAppBuilderForFileExplorer + + constructor() { + Commands.register('aws.appBuilder.viewDocs', () => { + void openUrl(debugNewSamAppDocUrl.toolkit) + telemetry.aws_help.emit({ name: 'appBuilder' }) + }) + this._refreshAppBuilderExplorer = (provider?: ResourceTreeDataProvider) => + Commands.register('aws.appBuilder.refresh', () => { + this.refresh() + if (provider) { + provider.refresh() + } + }) + + this._refreshAppBuilderForFileExplorer = (provider?: ResourceTreeDataProvider) => + Commands.register('aws.appBuilderForFileExplorer.refresh', () => { + this.refresh() + if (provider) { + provider.refresh() + } + }) + } + + public get refreshAppBuilderExplorer() { + return this._refreshAppBuilderExplorer + } + + public get refreshAppBuilderForFileExplorer() { + return this._refreshAppBuilderForFileExplorer + } + + public async getChildren() { + const nodesToReturn = await getAppNodes() + if (nodesToReturn.length === 0) { + return [] + } + + const walkthroughCompleted = globals.globalState.get('aws.toolkit.lambda.walkthroughCompleted') + // show walkthrough node if walkthrough not completed yet + if (!walkthroughCompleted) { + nodesToReturn.unshift(new WalkthroughNode()) + } + return nodesToReturn + } + + public refresh(): void { + this.onDidChangeChildrenEmitter.fire() + } + + public getTreeItem() { + const item = new vscode.TreeItem('APPLICATION BUILDER') + item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed + item.contextValue = 'awsAppBuilderRootNode' + + return item + } + + static #instance: AppBuilderRootNode + + static get instance(): AppBuilderRootNode { + return (this.#instance ??= new AppBuilderRootNode()) + } +} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/walkthroughNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/walkthroughNode.ts new file mode 100644 index 00000000000..8f3432075df --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/nodes/walkthroughNode.ts @@ -0,0 +1,37 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' +import { localize } from '../../../../shared/utilities/vsCodeUtils' + +/** + * Create Open Walkthrough Node in App builder sidebar + * + */ +export class WalkthroughNode implements TreeNode { + public readonly id = 'walkthrough' + public readonly resource: WalkthroughNode = this + + // Constructor left empty intentionally for future extensibility + public constructor() {} + + /** + * Generates the TreeItem for the Walkthrough Node. + * This item will appear in the sidebar with a label and command to open the walkthrough. + */ + public getTreeItem() { + const itemLabel = localize('AWS.appBuilder.openWalkthroughTitle', 'Walkthrough of Application Builder') + + const item = new vscode.TreeItem(itemLabel) + item.contextValue = 'awsWalkthroughNode' + item.command = { + title: localize('AWS.appBuilder.openWalkthroughTitle', 'Walkthrough of Application Builder'), + command: 'aws.toolkit.lambda.openWalkthrough', + } + + return item + } +} diff --git a/packages/core/src/awsService/appBuilder/explorer/openTemplate.ts b/packages/core/src/awsService/appBuilder/explorer/openTemplate.ts new file mode 100644 index 00000000000..9544efc51ed --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/openTemplate.ts @@ -0,0 +1,23 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { samSyncUrl } from '../../../shared/constants' +import { CloudFormationTemplateRegistry } from '../../../shared/fs/templateRegistry' +import { syncMementoRootKey } from '../../../shared/sam/sync' + +import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' +import { createTemplatePrompter, TemplateItem } from '../../../shared/ui/sam/templatePrompter' +import { Wizard } from '../../../shared/wizards/wizard' + +export interface OpenTemplateParams { + readonly template: TemplateItem +} + +export class OpenTemplateWizard extends Wizard { + public constructor(state: Partial, registry: CloudFormationTemplateRegistry) { + super({ initState: state, exitPrompterProvider: createExitPrompter }) + this.form.template.bindPrompter(() => createTemplatePrompter(registry, syncMementoRootKey, samSyncUrl)) + } +} diff --git a/packages/core/src/awsService/appBuilder/explorer/samProject.ts b/packages/core/src/awsService/appBuilder/explorer/samProject.ts new file mode 100644 index 00000000000..fd571cd6be8 --- /dev/null +++ b/packages/core/src/awsService/appBuilder/explorer/samProject.ts @@ -0,0 +1,103 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import * as CloudFormation from '../../../shared/cloudformation/cloudformation' +import { SamConfig, SamConfigErrorCode } from '../../../shared/sam/config' +import { getLogger } from '../../../shared/logger/logger' +import { ToolkitError } from '../../../shared/errors' +import { showViewLogsMessage } from '../../../shared/utilities/messages' + +export interface SamApp { + location: SamAppLocation + resourceTree: ResourceTreeEntity[] +} + +export interface SamAppLocation { + samTemplateUri: vscode.Uri + workspaceFolder: vscode.WorkspaceFolder + projectRoot: vscode.Uri +} + +export interface ResourceTreeEntity { + Id: string + Type: string + Runtime?: string + CodeUri?: string + Handler?: string + Events?: ResourceTreeEntity[] + Path?: string + Method?: string +} + +export async function getStackName(projectRoot: vscode.Uri): Promise { + try { + const samConfig = await SamConfig.fromProjectRoot(projectRoot) + const stackName = await samConfig.getCommandParam('global', 'stack_name') + const region = await samConfig.getCommandParam('global', 'region') + + return { stackName, region } + } catch (error: any) { + switch (error.code) { + case SamConfigErrorCode.samNoConfigFound: + getLogger().info('No stack name or region information available in samconfig.toml: %O', error) + break + case SamConfigErrorCode.samConfigParseError: + getLogger().error(`Error getting stack name or region information: ${error.message}`, error) + void showViewLogsMessage('Encountered an issue reading samconfig.toml') + break + default: + getLogger().warn(`Error getting stack name or region information: ${error.message}`, error) + } + return {} + } +} + +export async function getApp(location: SamAppLocation): Promise { + const samTemplate = await CloudFormation.tryLoad(location.samTemplateUri) + if (!samTemplate.template) { + throw new ToolkitError(`Template at ${location.samTemplateUri.fsPath} is not valid`) + } + const templateResources = getResourceEntity(samTemplate.template) + + const resourceTree = [...templateResources] + + return { location, resourceTree } +} + +function getResourceEntity(template: any): ResourceTreeEntity[] { + const resourceTree: ResourceTreeEntity[] = [] + + for (const [logicalId, resource] of Object.entries(template?.Resources ?? []) as [string, any][]) { + const resourceEntity: ResourceTreeEntity = { + Id: logicalId, + Type: resource.Type, + Runtime: resource.Properties?.Runtime ?? template?.Globals?.Function?.Runtime, + Handler: resource.Properties?.Handler ?? template?.Globals?.Function?.Handler, + Events: resource.Properties?.Events ? getEvents(resource.Properties.Events) : undefined, + CodeUri: resource.Properties?.CodeUri ?? template?.Globals?.Function?.CodeUri, + } + resourceTree.push(resourceEntity) + } + + return resourceTree +} + +function getEvents(events: Record): ResourceTreeEntity[] { + const eventResources: ResourceTreeEntity[] = [] + + for (const [eventsLogicalId, event] of Object.entries(events)) { + const eventProperties = event.Properties + const eventResource: ResourceTreeEntity = { + Id: eventsLogicalId, + Type: event.Type, + Path: eventProperties.Path, + Method: eventProperties.Method, + } + eventResources.push(eventResource) + } + + return eventResources +} diff --git a/packages/core/src/awsService/appBuilder/utils.ts b/packages/core/src/awsService/appBuilder/utils.ts new file mode 100644 index 00000000000..de3dee8770d --- /dev/null +++ b/packages/core/src/awsService/appBuilder/utils.ts @@ -0,0 +1,198 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { TreeNode } from '../../shared/treeview/resourceTreeDataProvider' +import * as nls from 'vscode-nls' +import { ResourceNode } from './explorer/nodes/resourceNode' +import type { SamAppLocation } from './explorer/samProject' +import { ToolkitError } from '../../shared/errors' +import globals from '../../shared/extensionGlobals' +import { OpenTemplateParams, OpenTemplateWizard } from './explorer/openTemplate' +import { DataQuickPickItem, createQuickPick } from '../../shared/ui/pickerPrompter' +import { createCommonButtons } from '../../shared/ui/buttons' +import { samDeployUrl } from '../../shared/constants' +import path from 'path' +import fs from '../../shared/fs/fs' +import { getLogger } from '../../shared/logger/logger' +import { RuntimeFamily, getFamily } from '../../lambda/models/samLambdaRuntime' +import { showMessage } from '../../shared/utilities/messages' +const localize = nls.loadMessageBundle() + +export async function runOpenTemplate(arg?: TreeNode) { + const templateUri = arg ? (arg.resource as SamAppLocation).samTemplateUri : await promptUserForTemplate() + if (!templateUri || !(await fs.exists(templateUri))) { + throw new ToolkitError('No template provided', { code: 'NoTemplateProvided' }) + } + const document = await vscode.workspace.openTextDocument(templateUri) + await vscode.window.showTextDocument(document) +} + +/** + * Find and open the lambda handler with given ResoruceNode + * If not found, a NoHandlerFound error will be raised + * @param arg ResourceNode + */ +export async function runOpenHandler(arg: ResourceNode): Promise { + const folderUri = path.dirname(arg.resource.location.fsPath) + if (!arg.resource.resource.CodeUri) { + throw new ToolkitError('No CodeUri provided in template, cannot open handler', { code: 'NoCodeUriProvided' }) + } + + if (!arg.resource.resource.Handler) { + throw new ToolkitError('No Handler provided in template, cannot open handler', { code: 'NoHandlerProvided' }) + } + + if (!arg.resource.resource.Runtime) { + throw new ToolkitError('No Runtime provided in template, cannot open handler', { code: 'NoRuntimeProvided' }) + } + + const handlerFile = await getLambdaHandlerFile( + vscode.Uri.file(folderUri), + arg.resource.resource.CodeUri, + arg.resource.resource.Handler, + arg.resource.resource.Runtime + ) + if (!handlerFile) { + throw new ToolkitError(`No handler file found with name "${arg.resource.resource.Handler}"`, { + code: 'NoHandlerFound', + }) + } + await vscode.workspace.openTextDocument(handlerFile).then(async (doc) => await vscode.window.showTextDocument(doc)) +} + +// create a set to store all supported runtime in the following function +const supportedRuntimeForHandler = new Set([ + RuntimeFamily.Ruby, + RuntimeFamily.Python, + RuntimeFamily.NodeJS, + RuntimeFamily.DotNet, + RuntimeFamily.Java, +]) + +/** + * Get the actual Lambda handler file, in vscode.Uri format, from the template + * file and handler name. If not found, return undefined. + * + * @param folderUri The root folder for sam project + * @param codeUri codeUri prop in sam template + * @param handler handler prop in sam template + * @param runtime runtime prop in sam template + * @returns + */ +export async function getLambdaHandlerFile( + folderUri: vscode.Uri, + codeUri: string, + handler: string, + runtime: string +): Promise { + const family = getFamily(runtime) + if (!supportedRuntimeForHandler.has(family)) { + throw new ToolkitError(`Runtime ${runtime} is not supported for open handler button`, { + code: 'RuntimeNotSupported', + }) + } + + const handlerParts = handler.split('.') + // sample: app.lambda_handler -> app.rb + if (family === RuntimeFamily.Ruby) { + // Ruby supports namespace/class handlers as well, but the path is + // guaranteed to be slash-delimited so we can assume the first part is + // the path + return vscode.Uri.joinPath(folderUri, codeUri, handlerParts.slice(0, handlerParts.length - 1).join('/') + '.rb') + } + + // sample:app.lambda_handler -> app.py + if (family === RuntimeFamily.Python) { + // Otherwise (currently Node.js and Python) handle dot-delimited paths + return vscode.Uri.joinPath(folderUri, codeUri, handlerParts.slice(0, handlerParts.length - 1).join('/') + '.py') + } + + // sample: app.handler -> app.mjs/app.js + // More likely to be mjs if NODEJS version>=18, now searching for both + if (family === RuntimeFamily.NodeJS) { + const handlerName = handlerParts.slice(0, handlerParts.length - 1).join('/') + const handlerPath = path.dirname(handlerName) + const handlerFile = path.basename(handlerName) + const pattern = new vscode.RelativePattern( + vscode.Uri.joinPath(folderUri, codeUri, handlerPath), + `${handlerFile}.{js,mjs}` + ) + return searchHandlerFile(folderUri, pattern) + } + // search directly under Code uri for Dotnet and java + // sample: ImageResize::ImageResize.Function::FunctionHandler -> Function.cs + if (family === RuntimeFamily.DotNet) { + const handlerName = path.basename(handler.split('::')[1].replaceAll('.', '/')) + const pattern = new vscode.RelativePattern(vscode.Uri.joinPath(folderUri, codeUri), `${handlerName}.cs`) + return searchHandlerFile(folderUri, pattern) + } + + // sample: resizer.App::handleRequest -> App.java + if (family === RuntimeFamily.Java) { + const handlerName = handler.split('::')[0].replaceAll('.', '/') + const pattern = new vscode.RelativePattern(vscode.Uri.joinPath(folderUri, codeUri), `**/${handlerName}.java`) + return searchHandlerFile(folderUri, pattern) + } +} + +/** + Searches for a handler file in the given pattern and returns the first match. + If no match is found, returns undefined. +*/ +export async function searchHandlerFile( + folderUri: vscode.Uri, + pattern: vscode.RelativePattern +): Promise { + const handlerFile = await vscode.workspace.findFiles(pattern, new vscode.RelativePattern(folderUri, '.aws-sam')) + if (handlerFile.length === 0) { + return undefined + } + if (handlerFile.length > 1) { + getLogger().warn(`Multiple handler files found with name "${path.basename(handlerFile[0].fsPath)}"`) + void showMessage('warn', `Multiple handler files found with name "${path.basename(handlerFile[0].fsPath)}"`) + } + if (await fs.exists(handlerFile[0])) { + return handlerFile[0] + } + return undefined +} + +async function promptUserForTemplate() { + const registry = await globals.templateRegistry + const openTemplateParams: Partial = {} + + const param = await new OpenTemplateWizard(openTemplateParams, registry).run() + return param?.template.uri +} + +export async function deployTypePrompt() { + const items: DataQuickPickItem[] = [ + { + label: 'Sync', + data: 'sync', + detail: 'Speed up your development and testing experience in the AWS Cloud. With the --watch parameter, sync will build, deploy and watch for local changes', + description: 'Development environments', + }, + { + label: 'Deploy', + data: 'deploy', + detail: 'Deploys your template through CloudFormation', + description: 'Production environments', + }, + ] + + const selected = await createQuickPick(items, { + title: localize('AWS.appBuilder.deployType.title', 'Select deployment command'), + placeholder: 'Press enter to proceed with highlighted option', + buttons: createCommonButtons(samDeployUrl), + }).prompt() + + if (!selected) { + getLogger().info('Operation cancelled.') + return + } + return selected +} diff --git a/packages/core/src/awsService/appBuilder/walkthrough.ts b/packages/core/src/awsService/appBuilder/walkthrough.ts new file mode 100644 index 00000000000..04f43d61878 --- /dev/null +++ b/packages/core/src/awsService/appBuilder/walkthrough.ts @@ -0,0 +1,349 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as semver from 'semver' +import * as vscode from 'vscode' +import globals from '../../shared/extensionGlobals' +import { getLogger } from '../../shared/logger' + +import { Wizard } from '../../shared/wizards/wizard' +import { createQuickPick } from '../../shared/ui/pickerPrompter' +import { createCommonButtons } from '../../shared/ui/buttons' +import * as nls from 'vscode-nls' +import { ToolkitError } from '../../shared/errors' +import { createSingleFileDialog } from '../../shared/ui/common/openDialog' +import { fs } from '../../shared/fs/fs' +import path from 'path' +import { telemetry } from '../../shared/telemetry' + +import { minSamCliVersionForAppBuilderSupport } from '../../shared/sam/cli/samCliValidator' +import { SamCliInfoInvocation } from '../../shared/sam/cli/samCliInfo' +import { openUrl } from '../../shared/utilities/vsCodeUtils' +import { getOrInstallCli, awsClis, AwsClis } from '../../shared/utilities/cliUtils' +import { getPattern } from '../../shared/utilities/downloadPatterns' +import { addFolderToWorkspace } from '../../shared/utilities/workspaceUtils' + +const localize = nls.loadMessageBundle() +const serverlessLandUrl = 'https://serverlessland.com/' +export const walkthroughContextString = 'aws.toolkit.lambda.walkthroughSelected' +export const templateToOpenAppComposer = 'aws.toolkit.appComposer.templateToOpenOnStart' +const defaultTemplateName = 'template.yaml' +const serverlessLandOwner = 'aws-samples' +const serverlessLandRepo = 'serverless-patterns' + +type WalkthroughOptions = 'CustomTemplate' | 'Visual' | 'S3' | 'API' +type TutorialRuntimeOptions = 'python' | 'node' | 'java' | 'dotnet' | 'skipped' + +interface IServerlessLandProject { + asset: string + handler?: string +} + +export const appMap = new Map([ + ['APIdotnet', { asset: 'apigw-rest-api-lambda-dotnet.zip', handler: 'src/HelloWorld/Function.cs' }], + ['APInode', { asset: 'apigw-rest-api-lambda-node.zip', handler: 'hello_world/app.mjs' }], + ['APIpython', { asset: 'apigw-rest-api-lambda-python.zip', handler: 'hello_world/app.py' }], + [ + 'APIjava', + { + asset: 'apigw-rest-api-lambda-java.zip', + handler: 'HelloWorldFunction/src/main/java/helloworld/App.java', + }, + ], + ['S3dotnet', { asset: 's3-lambda-resizing-dotnet.zip', handler: 'ImageResize/Function.cs' }], + ['S3node', { asset: 's3-lambda-resizing-node.zip', handler: 'src/app.js' }], + ['S3python', { asset: 's3-lambda-resizing-python.zip', handler: 'src/app.py' }], + [ + 'S3java', + { + asset: 's3-lambda-resizing-java.zip', + handler: 'ResizerFunction/src/main/java/resizer/App.java', + }, + ], +]) + +export class RuntimeLocationWizard extends Wizard<{ + runtime: TutorialRuntimeOptions + dir: string + realDir: vscode.Uri +}> { + public constructor(skipRuntime: boolean, labelValue: string, existingTemplates?: vscode.Uri[]) { + super() + const form = this.form + + // step1: choose runtime + const items: { label: string; data: TutorialRuntimeOptions }[] = [ + { label: 'Python', data: 'python' }, + { label: 'Node JS', data: 'node' }, + { label: 'Java', data: 'java' }, + { label: 'Dot Net', data: 'dotnet' }, + ] + form.runtime.bindPrompter( + () => { + return createQuickPick(items, { + title: localize('AWS.toolkit.lambda.walkthroughSelectRuntime', 'Select a runtime'), + buttons: createCommonButtons(serverlessLandUrl), + }) + }, + { showWhen: () => !skipRuntime } + ) + + // step2: choose location for project + const wsFolders = vscode.workspace.workspaceFolders + const items2 = [ + { + label: localize('AWS.toolkit.lambda.walkthroughOpenExplorer', 'Open file explorer'), + data: 'file-selector', + }, + ] + + // if at least one open workspace, add all opened workspace as options + if (wsFolders && labelValue !== 'Open existing Project') { + for (const wsFolder of wsFolders) { + items2.push({ label: wsFolder.uri.fsPath, data: wsFolder.uri.fsPath }) + } + } + + if (wsFolders && existingTemplates && labelValue === 'Open existing Project') { + existingTemplates.map((file) => { + items2.push({ label: file.fsPath, data: path.dirname(file.fsPath) }) + }) + } + + form.dir.bindPrompter(() => { + return createQuickPick(items2, { + title: + labelValue === 'Open existing Project' + ? localize('AWS.toolkit.lambda.walkthroughOpenExistProject', 'Select an existing template file') + : localize('AWS.toolkit.lambda.walkthroughProjectLocation', 'Select a location for project'), + buttons: createCommonButtons(labelValue === 'Open existing Project' ? undefined : serverlessLandUrl), + }) + }) + + const options: vscode.OpenDialogOptions = { + canSelectMany: false, + openLabel: labelValue, + canSelectFiles: false, + canSelectFolders: true, + } + if (wsFolders) { + options.defaultUri = wsFolders[0]?.uri + } + + form.realDir.bindPrompter((state) => createSingleFileDialog(options), { + showWhen: (state) => state.dir !== undefined && state.dir === 'file-selector', + setDefault: (state) => (state.dir ? vscode.Uri.file(state.dir) : undefined), + }) + } +} + +export async function getTutorial( + runtime: TutorialRuntimeOptions, + project: WalkthroughOptions, + outputDir: vscode.Uri, + source?: string +): Promise { + const appSelected = appMap.get(project + runtime) + telemetry.record({ action: project + runtime, source: source ?? 'AppBuilderWalkthrough' }) + if (!appSelected) { + throw new ToolkitError(`Tried to get template '${project}+${runtime}', but it hasn't been registered.`) + } + + try { + await getPattern(serverlessLandOwner, serverlessLandRepo, appSelected.asset, outputDir, true) + } catch (error) { + throw new ToolkitError(`Error occurred while fetching the pattern from serverlessland: ${error}`) + } +} + +/** + * Takes projectUri and runtime then generate matching project + * @param walkthroughSelected the selected walkthrough + * @param projectUri The choosen project uri to generate proejct + * @param runtime The runtime choosen + */ +export async function genWalkthroughProject( + walkthroughSelected: WalkthroughOptions, + projectUri: vscode.Uri, + runtime: TutorialRuntimeOptions | undefined +): Promise { + // create project here + // TODO update with file fetching from serverless land + if (walkthroughSelected === 'CustomTemplate') { + // customer already have a project, no need to generate + return + } + + // check if template.yaml already exists + const templateUri = vscode.Uri.joinPath(projectUri, defaultTemplateName) + if (await fs.exists(templateUri)) { + // ask if want to overwrite + const choice = await vscode.window.showInformationMessage( + localize( + 'AWS.toolkit.lambda.walkthroughCreateProjectPrompt', + '{0} already exist in the selected directory, overwrite?', + defaultTemplateName + ), + 'Yes', + 'No' + ) + if (choice !== 'Yes') { + throw new ToolkitError(`${defaultTemplateName} already exist`) + } + } + + // if Yes, or template not found, continue to generate + if (walkthroughSelected === 'Visual') { + // create an empty template.yaml, open it in appcomposer later + await fs.writeFile(templateUri, Buffer.from('')) + return + } + // start fetching project + if (runtime && runtime !== 'skipped') { + await getTutorial(runtime, walkthroughSelected, projectUri, 'AppBuilderWalkthrough') + } +} + +/** + * check if the selected project Uri exist in current workspace. If not, add Project folder to Workspace + * @param projectUri uri for the selected project + */ +export async function openProjectInWorkspace(projectUri: vscode.Uri): Promise { + let templateUri: vscode.Uri | undefined = vscode.Uri.joinPath(projectUri, defaultTemplateName) + if (!(await fs.exists(templateUri))) { + // no template.yaml, trying yml + templateUri = vscode.Uri.joinPath(projectUri, 'template.yml') + if (!(await fs.exists(templateUri))) { + templateUri = undefined + } + } + + // Open template file, and after update workspace folder + if (templateUri) { + await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup') + await vscode.window.showTextDocument(await vscode.workspace.openTextDocument(templateUri)) + // set global key to template to be opened, appComposer will open them upon reload + await globals.globalState.update(templateToOpenAppComposer, [templateUri.fsPath]) + } + + // if extension is reloaded here, this function will never return (killed) + await addFolderToWorkspace({ uri: projectUri, name: path.basename(projectUri.fsPath) }, true) + + // Open template file + if (templateUri) { + // extension not reloaded, set to false + await globals.globalState.update(templateToOpenAppComposer, undefined) + await vscode.commands.executeCommand('aws.openInApplicationComposer', templateUri) + } + + // Open Readme if exist + if (await fs.exists(vscode.Uri.joinPath(projectUri, 'README.md'))) { + await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup') + await vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.joinPath(projectUri, 'README.md')) + } +} + +/** + * Used in Toolkit Appbuilder Walkthrough. + * 1: Customer select a template + * 2: Create project / Or don't create if customer choose use my own template + * 3: Add project to workspace, Open template.yaml, open template.yaml in AppComposer + */ +export async function initWalkthroughProjectCommand() { + const walkthroughSelected = globals.globalState.get(walkthroughContextString) + let runtimeSelected: TutorialRuntimeOptions | undefined = undefined + try { + if (!walkthroughSelected || !(typeof walkthroughSelected === 'string')) { + getLogger().info('exit on no walkthrough selected') + void vscode.window.showErrorMessage( + localize('AWS.toolkit.lambda.walkthroughNotSelected', 'Please select a template first') + ) + return + } + let labelValue = 'Create Project' + if (walkthroughSelected === 'CustomTemplate') { + labelValue = 'Open existing Project' + } + // if these two, skip runtime choice + const skipRuntimeChoice = walkthroughSelected === 'Visual' || walkthroughSelected === 'CustomTemplate' + const templates: vscode.Uri[] = + walkthroughSelected === 'CustomTemplate' + ? await vscode.workspace.findFiles('**/{template.yaml,template.yml}', '**/.aws-sam/*') + : [] + const result = await new RuntimeLocationWizard(skipRuntimeChoice, labelValue, templates).run() + if (!result) { + getLogger().info('User canceled the runtime selection process via quickpick') + return + } + + if (!result.realDir || !fs.exists(result.realDir)) { + // exit for non-vaild uri + getLogger().info('exit on customer fileselector cancellation') + return + } + + runtimeSelected = result.runtime + + // generate project + await genWalkthroughProject(walkthroughSelected, result.realDir, runtimeSelected) + // open a workspace if no workspace yet + await openProjectInWorkspace(result.realDir) + } finally { + telemetry.record({ action: `${walkthroughSelected}:${runtimeSelected}`, source: 'AppBuilderWalkthrough' }) + } +} + +export async function getOrUpdateOrInstallSAMCli(source: string) { + try { + // find sam + const samPath = await getOrInstallCli('sam-cli', true, true) + // check version + const samCliVersion = (await new SamCliInfoInvocation(samPath).execute()).version + + if (semver.lt(samCliVersion, minSamCliVersionForAppBuilderSupport)) { + // sam found but version too low + const updateInstruction = localize( + 'AWS.toolkit.updateSAMInstruction', + 'View AWS SAM CLI update instructions' + ) + const selection = await vscode.window.showInformationMessage( + localize( + 'AWS.toolkit.samOutdatedPrompt', + 'AWS SAM CLI version {0} or greater is required ({1} currently installed).', + minSamCliVersionForAppBuilderSupport, + samCliVersion + ), + updateInstruction + ) + if (selection === updateInstruction) { + void openUrl(vscode.Uri.parse(awsClis['sam-cli'].manualInstallLink)) + } + } + } catch (err) { + throw ToolkitError.chain(err, 'Failed to install or detect SAM') + } finally { + telemetry.record({ source: source, toolId: 'sam-cli' }) + } +} + +/** + * wraps getOrinstallCli and send telemetry + * @param toolId to install/check + * @param source to be added in telemetry + */ +export async function getOrInstallCliWrapper(toolId: AwsClis, source: string) { + await telemetry.appBuilder_installTool.run(async (span) => { + span.record({ source: source, toolId: toolId }) + if (toolId === 'sam-cli') { + await getOrUpdateOrInstallSAMCli(source) + return + } + try { + await getOrInstallCli(toolId, true, true) + } finally { + telemetry.record({ source: source, toolId: toolId }) + } + }) +} diff --git a/packages/core/src/awsService/appBuilder/wizards/deployTypeWizard.ts b/packages/core/src/awsService/appBuilder/wizards/deployTypeWizard.ts new file mode 100644 index 00000000000..fbaec4657ca --- /dev/null +++ b/packages/core/src/awsService/appBuilder/wizards/deployTypeWizard.ts @@ -0,0 +1,54 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { samDeployUrl } from '../../../shared/constants' +import { createCommonButtons } from '../../../shared/ui/buttons' +import { DataQuickPickItem, createQuickPick } from '../../../shared/ui/pickerPrompter' +import * as nls from 'vscode-nls' +import { Wizard } from '../../../shared/wizards/wizard' +import { DeployParams, DeployWizard } from '../../../shared/sam/deploy' +import { SyncParams, SyncWizard } from '../../../shared/sam/sync' +import { WizardPrompter } from '../../../shared/ui/wizardPrompter' +import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' +const localize = nls.loadMessageBundle() + +export class DeployTypeWizard extends Wizard<{ + choice: string + syncParam: SyncParams + deployParam: DeployParams +}> { + public constructor(syncWizard: SyncWizard, deployWizard: DeployWizard) { + super({ exitPrompterProvider: createExitPrompter }) + const form = this.form + + const items: DataQuickPickItem[] = [ + { + label: 'Sync', + data: 'sync', + detail: 'Speed up your development and testing experience in the AWS Cloud. With the --watch parameter, sync will build, deploy and watch for local changes', + description: 'Development environments', + }, + { + label: 'Deploy', + data: 'deploy', + detail: 'Deploys your template through CloudFormation', + description: 'Production environments', + }, + ] + form.choice.bindPrompter(() => { + return createQuickPick(items, { + title: localize('AWS.appBuilder.deployType.title', 'Select deployment command'), + placeholder: 'Press enter to proceed with highlighted option', + buttons: createCommonButtons(samDeployUrl), + }) + }) + form.deployParam.bindPrompter((state) => new WizardPrompter(deployWizard), { + showWhen: (state) => state.choice === 'deploy', + }) + form.syncParam.bindPrompter((state) => new WizardPrompter(syncWizard), { + showWhen: (state) => state.choice === 'sync', + }) + } +} diff --git a/packages/core/src/awsService/appBuilder/wizards/templateParametersWizard.ts b/packages/core/src/awsService/appBuilder/wizards/templateParametersWizard.ts new file mode 100644 index 00000000000..75c80c4eda9 --- /dev/null +++ b/packages/core/src/awsService/appBuilder/wizards/templateParametersWizard.ts @@ -0,0 +1,62 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { Wizard } from '../../../shared/wizards/wizard' +import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' +import * as CloudFormation from '../../../shared/cloudformation/cloudformation' +import { createInputBox } from '../../../shared/ui/inputPrompter' +import { createCommonButtons } from '../../../shared/ui/buttons' +import { getRecentResponse, updateRecentResponse } from '../../../shared/sam/utils' +import { getParameters } from '../../../lambda/config/parameterUtils' + +export interface TemplateParametersForm { + [key: string]: any +} + +export class TemplateParametersWizard extends Wizard { + template: vscode.Uri + preloadedTemplate: CloudFormation.Template | undefined + samTemplateParameters: Map | undefined + samCommandUrl: vscode.Uri + commandMementoRootKey: string + + public constructor(template: vscode.Uri, samCommandUrl: vscode.Uri, commandMementoRootKey: string) { + super({ exitPrompterProvider: createExitPrompter }) + this.template = template + this.samCommandUrl = samCommandUrl + this.commandMementoRootKey = commandMementoRootKey + } + + public override async init(): Promise { + this.samTemplateParameters = await getParameters(this.template) + this.preloadedTemplate = await CloudFormation.load(this.template.fsPath) + const samTemplateNames = new Set(this.samTemplateParameters?.keys() ?? []) + + samTemplateNames.forEach((name) => { + if (this.preloadedTemplate) { + const defaultValue = this.preloadedTemplate.Parameters + ? (this.preloadedTemplate.Parameters[name]?.Default as string) + : undefined + this.form[name].bindPrompter(() => + this.createParamPromptProvider(name, defaultValue).transform(async (item) => { + await updateRecentResponse(this.commandMementoRootKey, this.template.fsPath, name, item) + return item + }) + ) + } + }) + + return this + } + + createParamPromptProvider(name: string, defaultValue: string | undefined) { + return createInputBox({ + title: `Specify SAM Template parameter value for ${name}`, + buttons: createCommonButtons(this.samCommandUrl), + value: getRecentResponse(this.commandMementoRootKey, this.template.fsPath, name) ?? defaultValue, + }) + } +} diff --git a/packages/core/src/awsService/apprunner/commands/pauseService.ts b/packages/core/src/awsService/apprunner/commands/pauseService.ts index a1f3bac54c9..b2cbdee1cae 100644 --- a/packages/core/src/awsService/apprunner/commands/pauseService.ts +++ b/packages/core/src/awsService/apprunner/commands/pauseService.ts @@ -18,7 +18,7 @@ export async function pauseService(node: AppRunnerServiceNode): Promise { try { const prompts = ToolkitPromptSettings.instance - const shouldNotify = await prompts.isPromptEnabled('apprunnerNotifyPause') + const shouldNotify = prompts.isPromptEnabled('apprunnerNotifyPause') const notifyPrompt = localize( 'aws.apprunner.pauseService.notify', 'Your service will be unavailable while paused. ' + diff --git a/packages/core/src/awsService/apprunner/wizards/apprunnerCreateServiceWizard.ts b/packages/core/src/awsService/apprunner/wizards/apprunnerCreateServiceWizard.ts index 14ce81b8dcd..99883ba6cf3 100644 --- a/packages/core/src/awsService/apprunner/wizards/apprunnerCreateServiceWizard.ts +++ b/packages/core/src/awsService/apprunner/wizards/apprunnerCreateServiceWizard.ts @@ -14,11 +14,11 @@ import { AppRunnerImageRepositoryWizard } from './imageRepositoryWizard' import { AppRunnerCodeRepositoryWizard } from './codeRepositoryWizard' import { GitExtension } from '../../../shared/extensions/git' import { makeDeploymentButton } from './deploymentButton' -import { apprunnerCreateServiceDocsUrl } from '../../../shared/constants' import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' import { DefaultIamClient } from '../../../shared/clients/iamClient' import { DefaultEcrClient } from '../../../shared/clients/ecrClient' import { DefaultAppRunnerClient } from '../../../shared/clients/apprunnerClient' +import { getAppRunnerCreateServiceDocUrl } from '../../../shared/extensionUtilities' const localize = nls.loadMessageBundle() @@ -68,7 +68,7 @@ function createInstanceStep(): Prompter { return picker.createQuickPick(items, { title: localize('AWS.apprunner.createService.selectInstanceConfig.title', 'Select instance configuration'), - buttons: createCommonButtons(apprunnerCreateServiceDocsUrl), + buttons: createCommonButtons(getAppRunnerCreateServiceDocUrl()), }) } @@ -92,7 +92,7 @@ function createSourcePrompter( return picker.createQuickPick([ecrPath, repositoryPath], { title: localize('AWS.apprunner.createService.sourceType.title', 'Select a source code location type'), - buttons: [autoDeployButton, ...createCommonButtons(apprunnerCreateServiceDocsUrl)], + buttons: [autoDeployButton, ...createCommonButtons(getAppRunnerCreateServiceDocUrl())], }) } @@ -137,7 +137,7 @@ export class CreateAppRunnerServiceWizard extends Wizard createStartCommandPrompter(state.Runtime!)) codeConfigForm.body.Port.bindPrompter(createPortPrompter) codeConfigForm.body.RuntimeEnvironmentVariables.bindPrompter(() => - createVariablesPrompter(createCommonButtons(apprunnerCreateServiceDocsUrl)) + createVariablesPrompter(createCommonButtons(getAppRunnerCreateServiceDocUrl())) ) // TODO: ask user if they would like to save their parameters into an App Runner config file diff --git a/packages/core/src/awsService/apprunner/wizards/deploymentButton.ts b/packages/core/src/awsService/apprunner/wizards/deploymentButton.ts index 24a98895970..dee77c05e97 100644 --- a/packages/core/src/awsService/apprunner/wizards/deploymentButton.ts +++ b/packages/core/src/awsService/apprunner/wizards/deploymentButton.ts @@ -32,7 +32,7 @@ function makeDeployButtons() { async function showDeploymentCostNotification(): Promise { const settings = ToolkitPromptSettings.instance - if (await settings.isPromptEnabled('apprunnerNotifyPricing')) { + if (settings.isPromptEnabled('apprunnerNotifyPricing')) { const notice = localize( 'aws.apprunner.createService.priceNotice.message', 'App Runner automatic deployments incur an additional cost.' diff --git a/packages/core/src/awsService/apprunner/wizards/imageRepositoryWizard.ts b/packages/core/src/awsService/apprunner/wizards/imageRepositoryWizard.ts index b514c4f233e..17acc53ed9d 100644 --- a/packages/core/src/awsService/apprunner/wizards/imageRepositoryWizard.ts +++ b/packages/core/src/awsService/apprunner/wizards/imageRepositoryWizard.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as vscode from 'vscode' import { AppRunner, IAM } from 'aws-sdk' import { createCommonButtons, QuickInputButton, QuickInputToggleButton } from '../../../shared/ui/buttons' import { toArrayAsync } from '../../../shared/utilities/collectionUtils' @@ -21,8 +20,7 @@ import { makeDeploymentButton } from './deploymentButton' import { IamClient } from '../../../shared/clients/iamClient' import { createRolePrompter } from '../../../shared/ui/common/roles' import { getLogger } from '../../../shared/logger/logger' -import { isCloud9 } from '../../../shared/extensionUtilities' -import { apprunnerCreateServiceDocsUrl } from '../../../shared/constants' +import { getAppRunnerCreateServiceDocUrl, isCloud9 } from '../../../shared/extensionUtilities' import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' const localize = nls.loadMessageBundle() @@ -162,7 +160,7 @@ function createPortPrompter(): Prompter { validateInput: validatePort, title: localize('AWS.apprunner.createService.selectPort.title', 'Enter a port for the new service'), placeholder: 'Enter a port', - buttons: createCommonButtons(apprunnerCreateServiceDocsUrl), + buttons: createCommonButtons(getAppRunnerCreateServiceDocUrl()), }) } @@ -249,7 +247,7 @@ function createImageRepositorySubForm( form.ImageConfiguration.Port.bindPrompter(() => createPortPrompter()) form.ImageConfiguration.RuntimeEnvironmentVariables.bindPrompter(() => - createVariablesPrompter(createCommonButtons(apprunnerCreateServiceDocsUrl)) + createVariablesPrompter(createCommonButtons(getAppRunnerCreateServiceDocUrl())) ) return subform @@ -262,7 +260,7 @@ export class AppRunnerImageRepositoryWizard extends Wizard { return createRolePrompter(iamClient, { title: localize('AWS.apprunner.createService.selectRole.title', 'Select a role to pull from ECR'), - helpUrl: vscode.Uri.parse(apprunnerCreateServiceDocsUrl), + helpUrl: getAppRunnerCreateServiceDocUrl(), roleFilter: (role) => (role.AssumeRolePolicyDocument ?? '').includes(appRunnerEcrEntity), createRole: createEcrRole.bind(undefined, iamClient), }).transform((resp) => resp.Arn) diff --git a/packages/core/src/awsService/cdk/explorer/cdkProject.ts b/packages/core/src/awsService/cdk/explorer/cdkProject.ts index c355d1ed748..c90fd3aa214 100644 --- a/packages/core/src/awsService/cdk/explorer/cdkProject.ts +++ b/packages/core/src/awsService/cdk/explorer/cdkProject.ts @@ -18,7 +18,7 @@ export interface CdkAppLocation { } export async function getApp(location: CdkAppLocation): Promise { - const constructTree = JSON.parse(await fs.readFileAsString(location.treeUri)) as ConstructTree + const constructTree = JSON.parse(await fs.readFileText(location.treeUri)) as ConstructTree return { location, constructTree } } diff --git a/packages/core/src/awsService/cloudWatchLogs/activation.ts b/packages/core/src/awsService/cloudWatchLogs/activation.ts index a186a8ba983..bfcaaf1d0e2 100644 --- a/packages/core/src/awsService/cloudWatchLogs/activation.ts +++ b/packages/core/src/awsService/cloudWatchLogs/activation.ts @@ -4,7 +4,7 @@ */ import * as vscode from 'vscode' -import { CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants' +import { cloudwatchLogsLiveTailScheme, CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants' import { Settings } from '../../shared/settings' import { addLogEvents } from './commands/addLogEvents' import { copyLogResource } from './commands/copyLogResource' @@ -19,12 +19,22 @@ import { searchLogGroup } from './commands/searchLogGroup' import { changeLogSearchParams } from './changeLogSearch' import { CloudWatchLogsNode } from './explorer/cloudWatchLogsNode' import { loadAndOpenInitialLogStreamFile, LogStreamCodeLensProvider } from './document/logStreamsCodeLensProvider' +import { clearDocument, closeSession, tailLogGroup } from './commands/tailLogGroup' +import { LiveTailDocumentProvider } from './document/liveTailDocumentProvider' +import { LiveTailSessionRegistry } from './registry/liveTailSessionRegistry' +import { DeployedResourceNode } from '../appBuilder/explorer/nodes/deployedNode' +import { isTreeNode } from '../../shared/treeview/resourceTreeDataProvider' +import { getLogger } from '../../shared/logger/logger' +import { ToolkitError } from '../../shared' +import { LiveTailCodeLensProvider } from './document/liveTailCodeLensProvider' export async function activate(context: vscode.ExtensionContext, configuration: Settings): Promise { const registry = LogDataRegistry.instance + const liveTailRegistry = LiveTailSessionRegistry.instance const documentProvider = new LogDataDocumentProvider(registry) - + const liveTailDocumentProvider = new LiveTailDocumentProvider() + const liveTailCodeLensProvider = new LiveTailCodeLensProvider(liveTailRegistry) context.subscriptions.push( vscode.languages.registerCodeLensProvider( { @@ -39,6 +49,20 @@ export async function activate(context: vscode.ExtensionContext, configuration: vscode.workspace.registerTextDocumentContentProvider(CLOUDWATCH_LOGS_SCHEME, documentProvider) ) + context.subscriptions.push( + vscode.languages.registerCodeLensProvider( + { + language: 'log', + scheme: cloudwatchLogsLiveTailScheme, + }, + liveTailCodeLensProvider + ) + ) + + context.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider(cloudwatchLogsLiveTailScheme, liveTailDocumentProvider) + ) + context.subscriptions.push( vscode.workspace.onDidCloseTextDocument((doc) => { if (doc.isClosed && doc.uri.scheme === CLOUDWATCH_LOGS_SCHEME) { @@ -89,6 +113,44 @@ export async function activate(context: vscode.ExtensionContext, configuration: Commands.register('aws.cwl.changeFilterPattern', async () => changeLogSearchParams(registry, 'filterPattern')), - Commands.register('aws.cwl.changeTimeFilter', async () => changeLogSearchParams(registry, 'timeFilter')) + Commands.register('aws.cwl.changeTimeFilter', async () => changeLogSearchParams(registry, 'timeFilter')), + + Commands.register('aws.cwl.tailLogGroup', async (node: LogGroupNode | CloudWatchLogsNode) => { + const logGroupInfo = + node instanceof LogGroupNode + ? { regionName: node.regionCode, groupName: node.logGroup.logGroupName! } + : undefined + const source = node ? (logGroupInfo ? 'ExplorerLogGroupNode' : 'ExplorerServiceNode') : 'Command' + await tailLogGroup(liveTailRegistry, source, liveTailCodeLensProvider, logGroupInfo) + }), + + Commands.register('aws.cwl.stopTailingLogGroup', async (document: vscode.TextDocument, source: string) => { + closeSession(document.uri, liveTailRegistry, source, liveTailCodeLensProvider) + }), + + Commands.register('aws.cwl.clearDocument', async (document: vscode.TextDocument) => { + await clearDocument(document) + }), + + Commands.register('aws.appBuilder.searchLogs', async (node: DeployedResourceNode) => { + try { + const logGroupInfo = isTreeNode(node) + ? { + regionName: node.resource.regionCode, + groupName: getFunctionLogGroupName(node.resource.explorerNode.configuration), + } + : undefined + const source: string = logGroupInfo ? 'AppBuilderSearchLogs' : 'CommandPaletteSearchLogs' + await searchLogGroup(registry, source, logGroupInfo) + } catch (err) { + getLogger().error('Failed to search logs: %s', err) + throw ToolkitError.chain(err, 'Failed to search logs') + } + }) ) } + +function getFunctionLogGroupName(configuration: any) { + const logGroupPrefix = '/aws/lambda/' + return configuration.logGroupName || logGroupPrefix + configuration.FunctionName +} diff --git a/packages/core/src/awsService/cloudWatchLogs/changeLogSearch.ts b/packages/core/src/awsService/cloudWatchLogs/changeLogSearch.ts index 4797e06fc03..349f7339e87 100644 --- a/packages/core/src/awsService/cloudWatchLogs/changeLogSearch.ts +++ b/packages/core/src/awsService/cloudWatchLogs/changeLogSearch.ts @@ -5,7 +5,7 @@ import { CancellationError } from '../../shared/utilities/timeoutUtils' import { telemetry } from '../../shared/telemetry/telemetry' import { showInputBox } from '../../shared/ui/inputPrompter' -import { createURIFromArgs, isLogStreamUri, recordTelemetryFilter } from './cloudWatchLogsUtils' +import { cwlUriSchema, isLogStreamUri, recordTelemetryFilter } from './cloudWatchLogsUtils' import { prepareDocument } from './commands/searchLogGroup' import { getActiveDocumentUri } from './document/logDataDocumentProvider' import { CloudWatchLogsData, filterLogEventsFromUri, LogDataRegistry } from './registry/logDataRegistry' @@ -98,7 +98,7 @@ export async function changeLogSearchParams( throw new CancellationError('user') } - const newUri = createURIFromArgs(newData.logGroupInfo, newData.parameters) + const newUri = cwlUriSchema.form({ logGroupInfo: newData.logGroupInfo, parameters: newData.parameters }) await prepareDocument(newUri, newData, registry) }) } diff --git a/packages/core/src/awsService/cloudWatchLogs/cloudWatchLogsUtils.ts b/packages/core/src/awsService/cloudWatchLogs/cloudWatchLogsUtils.ts index b8c769e92f8..e2356595153 100644 --- a/packages/core/src/awsService/cloudWatchLogs/cloudWatchLogsUtils.ts +++ b/packages/core/src/awsService/cloudWatchLogs/cloudWatchLogsUtils.ts @@ -6,8 +6,9 @@ import { telemetry } from '../../shared/telemetry/telemetry' import * as vscode from 'vscode' import { CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants' import { fromExtensionManifest } from '../../shared/settings' -import { CloudWatchLogsData, CloudWatchLogsGroupInfo } from './registry/logDataRegistry' +import { CloudWatchLogsArgs, CloudWatchLogsData, CloudWatchLogsGroupInfo } from './registry/logDataRegistry' import { CloudWatchLogsParameters } from './registry/logDataRegistry' +import { UriSchema } from '../../shared/utilities/uriUtils' // URIs are the only vehicle for delivering information to a TextDocumentContentProvider. // The following functions are used to structure and destructure relevant information to/from a URI. @@ -32,8 +33,7 @@ export function recordTelemetryFilter(logData: CloudWatchLogsData): void { export function uriToKey(uri: vscode.Uri): string { if (uri.query) { try { - const { filterPattern, startTime, endTime, limit, streamNameOptions } = - parseCloudWatchLogsUri(uri).parameters + const { filterPattern, startTime, endTime, limit, streamNameOptions } = cwlUriSchema.parse(uri).parameters const parts = [uri.path, filterPattern, startTime, endTime, limit, streamNameOptions] return parts.map((p) => p ?? '').join(':') } catch { @@ -52,7 +52,7 @@ export function uriToKey(uri: vscode.Uri): string { * message as the actual log group search. That results in a more fluid UX. */ export function msgKey(logGroupInfo: CloudWatchLogsGroupInfo): string { - const uri = createURIFromArgs(logGroupInfo, {}) + const uri = cwlUriSchema.form({ logGroupInfo: logGroupInfo, parameters: {} }) return uri.toString() } @@ -60,10 +60,7 @@ export function msgKey(logGroupInfo: CloudWatchLogsGroupInfo): string { * Destructures an awsCloudWatchLogs URI into its component pieces. * @param uri URI for a Cloudwatch Logs file */ -export function parseCloudWatchLogsUri(uri: vscode.Uri): { - logGroupInfo: CloudWatchLogsGroupInfo - parameters: CloudWatchLogsParameters -} { +function parseCloudWatchLogsUri(uri: vscode.Uri): CloudWatchLogsArgs { const parts = uri.path.split(':') if (uri.scheme !== CLOUDWATCH_LOGS_SCHEME) { @@ -85,18 +82,8 @@ export function parseCloudWatchLogsUri(uri: vscode.Uri): { } } -/** True if given URI is a valid Cloud Watch Logs Uri */ -export function isCwlUri(uri: vscode.Uri): boolean { - try { - parseCloudWatchLogsUri(uri) - return true - } catch { - return false - } -} - export function patternFromCwlUri(uri: vscode.Uri): CloudWatchLogsParameters['filterPattern'] { - return parseCloudWatchLogsUri(uri).parameters.filterPattern + return cwlUriSchema.parse(uri).parameters.filterPattern } /** @@ -105,7 +92,7 @@ export function patternFromCwlUri(uri: vscode.Uri): CloudWatchLogsParameters['fi * @returns */ export function isLogStreamUri(uri: vscode.Uri): boolean { - const logGroupInfo = parseCloudWatchLogsUri(uri).logGroupInfo + const logGroupInfo = cwlUriSchema.parse(uri).logGroupInfo return logGroupInfo.streamName !== undefined } @@ -115,15 +102,13 @@ export function isLogStreamUri(uri: vscode.Uri): boolean { * @param streamName Log stream name * @param regionName AWS region */ -export function createURIFromArgs( - logGroupInfo: CloudWatchLogsGroupInfo, - parameters: CloudWatchLogsParameters -): vscode.Uri { - let uriStr = `${CLOUDWATCH_LOGS_SCHEME}:${logGroupInfo.regionName}:${logGroupInfo.groupName}` - uriStr += logGroupInfo.streamName ? `:${logGroupInfo.streamName}` : '' +function createURIFromArgs(args: CloudWatchLogsArgs): vscode.Uri { + let uriStr = `${CLOUDWATCH_LOGS_SCHEME}:${args.logGroupInfo.regionName}:${args.logGroupInfo.groupName}` + uriStr += args.logGroupInfo.streamName ? `:${args.logGroupInfo.streamName}` : '' - uriStr += `?${encodeURIComponent(JSON.stringify(parameters))}` + uriStr += `?${encodeURIComponent(JSON.stringify(args.parameters))}` return vscode.Uri.parse(uriStr) } +export const cwlUriSchema = new UriSchema(parseCloudWatchLogsUri, createURIFromArgs) export class CloudWatchLogsSettings extends fromExtensionManifest('aws.cwl', { limit: Number }) {} diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/copyLogResource.ts b/packages/core/src/awsService/cloudWatchLogs/commands/copyLogResource.ts index b7cca40c752..846f87e797e 100644 --- a/packages/core/src/awsService/cloudWatchLogs/commands/copyLogResource.ts +++ b/packages/core/src/awsService/cloudWatchLogs/commands/copyLogResource.ts @@ -7,7 +7,7 @@ import * as nls from 'vscode-nls' const localize = nls.loadMessageBundle() import * as vscode from 'vscode' -import { isLogStreamUri, parseCloudWatchLogsUri } from '../cloudWatchLogsUtils' +import { cwlUriSchema, isLogStreamUri } from '../cloudWatchLogsUtils' import { copyToClipboard } from '../../../shared/utilities/messages' export async function copyLogResource(uri?: vscode.Uri): Promise { @@ -20,7 +20,7 @@ export async function copyLogResource(uri?: vscode.Uri): Promise { throw new Error('no active text editor, or undefined URI') } } - const parsedUri = parseCloudWatchLogsUri(uri) + const parsedUri = cwlUriSchema.parse(uri) const resourceName = isLogStreamUri(uri) ? parsedUri.logGroupInfo.streamName : parsedUri.logGroupInfo.groupName if (!resourceName) { diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.ts b/packages/core/src/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.ts index 2e83e04cf76..9d1600fafba 100644 --- a/packages/core/src/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.ts +++ b/packages/core/src/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode' import * as nls from 'vscode-nls' const localize = nls.loadMessageBundle() -import { isLogStreamUri, parseCloudWatchLogsUri } from '../cloudWatchLogsUtils' +import { cwlUriSchema, isLogStreamUri } from '../cloudWatchLogsUtils' import { telemetry, CloudWatchResourceType, Result } from '../../../shared/telemetry/telemetry' import fs from '../../../shared/fs/fs' @@ -28,7 +28,7 @@ export async function saveCurrentLogDataContent(): Promise { const workspaceDir = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri : vscode.Uri.file(fs.getUserHomeDir()) - const uriComponents = parseCloudWatchLogsUri(uri) + const uriComponents = cwlUriSchema.parse(uri) const logGroupInfo = uriComponents.logGroupInfo const localizedLogFile = localize('AWS.command.saveCurrentLogDataContent.logfile', 'Log File') diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/searchLogGroup.ts b/packages/core/src/awsService/cloudWatchLogs/commands/searchLogGroup.ts index 0dfc491bbb4..41ce8d7922f 100644 --- a/packages/core/src/awsService/cloudWatchLogs/commands/searchLogGroup.ts +++ b/packages/core/src/awsService/cloudWatchLogs/commands/searchLogGroup.ts @@ -16,7 +16,7 @@ import { } from '../registry/logDataRegistry' import { DataQuickPickItem } from '../../../shared/ui/pickerPrompter' import { isValidResponse, isWizardControl, Wizard, WIZARD_RETRY } from '../../../shared/wizards/wizard' -import { createURIFromArgs, msgKey, parseCloudWatchLogsUri, recordTelemetryFilter } from '../cloudWatchLogsUtils' +import { cwlUriSchema, msgKey, recordTelemetryFilter } from '../cloudWatchLogsUtils' import { DefaultCloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogsClient' import { CancellationError } from '../../../shared/utilities/timeoutUtils' import { getLogger } from '../../../shared/logger' @@ -29,6 +29,7 @@ import { createBackButton, createExitButton, createHelpButton } from '../../../s import { PromptResult } from '../../../shared/ui/prompter' import { ToolkitError } from '../../../shared/errors' import { Messages } from '../../../shared/utilities/messages' +import { showFile } from '../../../shared/utilities/textDocumentUtilities' const localize = nls.loadMessageBundle() @@ -65,9 +66,7 @@ export async function prepareDocument(uri: vscode.Uri, logData: CloudWatchLogsDa try { // Gets the data: calls filterLogEventsFromUri(). await registry.fetchNextLogEvents(uri) - const doc = await vscode.workspace.openTextDocument(uri) - await vscode.window.showTextDocument(doc, { preview: false }) - await vscode.languages.setTextDocumentLanguage(doc, 'log') + await showFile(uri) } catch (err) { if (CancellationError.isUserCancelled(err)) { throw err @@ -78,7 +77,7 @@ export async function prepareDocument(uri: vscode.Uri, logData: CloudWatchLogsDa localize( 'AWS.cwl.searchLogGroup.errorRetrievingLogs', 'Failed to get logs for {0}', - parseCloudWatchLogsUri(uri).logGroupInfo.groupName + cwlUriSchema.parse(uri).logGroupInfo.groupName ) ) } @@ -105,7 +104,7 @@ export async function searchLogGroup( } const userResponse = handleWizardResponse(response, registry) - const uri = createURIFromArgs(userResponse.logGroupInfo, userResponse.parameters) + const uri = cwlUriSchema.form({ logGroupInfo: userResponse.logGroupInfo, parameters: userResponse.parameters }) await prepareDocument(uri, userResponse, registry) }) } diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts new file mode 100644 index 00000000000..4aa1feb272a --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts @@ -0,0 +1,283 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { telemetry } from '../../../shared/telemetry/telemetry' +import { TailLogGroupWizard } from '../wizard/tailLogGroupWizard' +import { CancellationError } from '../../../shared/utilities/timeoutUtils' +import { LiveTailSession, LiveTailSessionConfiguration } from '../registry/liveTailSession' +import { LiveTailSessionRegistry } from '../registry/liveTailSessionRegistry' +import { + LiveTailSessionLogEvent, + LiveTailSessionUpdate, + StartLiveTailResponseStream, +} from '@aws-sdk/client-cloudwatch-logs' +import { getLogger, globals, ToolkitError } from '../../../shared' +import { uriToKey } from '../cloudWatchLogsUtils' +import { LiveTailCodeLensProvider } from '../document/liveTailCodeLensProvider' + +export async function tailLogGroup( + registry: LiveTailSessionRegistry, + source: string, + codeLensProvider: LiveTailCodeLensProvider, + logData?: { regionName: string; groupName: string } +): Promise { + await telemetry.cloudwatchlogs_startLiveTail.run(async (span) => { + const wizard = new TailLogGroupWizard(logData) + const wizardResponse = await wizard.run() + if (!wizardResponse) { + throw new CancellationError('user') + } + if (wizardResponse.logStreamFilter.type === 'menu' || wizardResponse.logStreamFilter.type === undefined) { + // logstream filter wizard uses type to determine which submenu to show. 'menu' is set when no type is selected + // and to show the 'menu' of selecting a type. This should not be reachable due to the picker logic, but validating in case. + throw new ToolkitError(`Invalid Log Stream filter type: ${wizardResponse.logStreamFilter.type}`) + } + const awsCredentials = await globals.awsContext.getCredentials() + if (awsCredentials === undefined) { + throw new ToolkitError('Failed to start LiveTail session: credentials are undefined.') + } + const liveTailSessionConfig: LiveTailSessionConfiguration = { + logGroupArn: wizardResponse.regionLogGroupSubmenuResponse.data, + logStreamFilter: wizardResponse.logStreamFilter, + logEventFilterPattern: wizardResponse.filterPattern, + region: wizardResponse.regionLogGroupSubmenuResponse.region, + awsCredentials: awsCredentials, + } + const session = new LiveTailSession(liveTailSessionConfig) + if (registry.has(uriToKey(session.uri))) { + await vscode.window.showTextDocument(session.uri, { preview: false }) + void vscode.window.showInformationMessage(`Switching editor to an existing session that matches request.`) + span.record({ + result: 'Succeeded', + sessionAlreadyStarted: true, + source: source, + }) + return + } + const document = await prepareDocument(session) + + const disposables: vscode.Disposable[] = [] + disposables.push(hideShowStatusBarItemsOnActiveEditor(session, document)) + disposables.push(closeSessionWhenAllEditorsClosed(session, registry, document, codeLensProvider)) + + try { + const stream = await session.startLiveTailSession() + registry.set(uriToKey(session.uri), session) + codeLensProvider.refresh() + getLogger().info(`LiveTail session started: ${uriToKey(session.uri)}`) + span.record({ + source: source, + result: 'Succeeded', + sessionAlreadyStarted: false, + hasTextFilter: Boolean(wizardResponse.filterPattern), + filterType: wizardResponse.logStreamFilter.type, + }) + await handleSessionStream(stream, document, session) + } finally { + disposables.forEach((disposable) => disposable.dispose()) + } + }) +} + +export function closeSession( + sessionUri: vscode.Uri, + registry: LiveTailSessionRegistry, + source: string, + codeLensProvider: LiveTailCodeLensProvider +) { + telemetry.cloudwatchlogs_stopLiveTail.run((span) => { + const session = registry.get(uriToKey(sessionUri)) + if (session === undefined) { + throw new ToolkitError(`No LiveTail session found for URI: ${uriToKey(sessionUri)}`) + } + session.stopLiveTailSession() + registry.delete(uriToKey(sessionUri)) + void vscode.window.showInformationMessage(`Stopped LiveTail session: ${uriToKey(sessionUri)}`) + codeLensProvider.refresh() + span.record({ + result: 'Succeeded', + source: source, + duration: session.getLiveTailSessionDuration(), + }) + }) +} + +export async function clearDocument(textDocument: vscode.TextDocument) { + const edit = new vscode.WorkspaceEdit() + const startPosition = new vscode.Position(0, 0) + const endPosition = new vscode.Position(textDocument.lineCount, 0) + edit.delete(textDocument.uri, new vscode.Range(startPosition, endPosition)) + await vscode.workspace.applyEdit(edit) +} + +async function prepareDocument(session: LiveTailSession): Promise { + const textDocument = await vscode.workspace.openTextDocument(session.uri) + await clearDocument(textDocument) + await vscode.window.showTextDocument(textDocument, { preview: false }) + await vscode.languages.setTextDocumentLanguage(textDocument, 'log') + session.showStatusBarItem(true) + return textDocument +} + +async function handleSessionStream( + stream: AsyncIterable, + document: vscode.TextDocument, + session: LiveTailSession +) { + try { + for await (const event of stream) { + if (event.sessionUpdate !== undefined && event.sessionUpdate.sessionResults !== undefined) { + const formattedLogEvents = event.sessionUpdate.sessionResults.map((logEvent) => + formatLogEvent(logEvent) + ) + if (formattedLogEvents.length !== 0) { + // Determine should scroll before adding new lines to doc because adding large + // amount of new lines can push bottom of file out of view before scrolling. + const editorsToScroll = getTextEditorsToScroll(document) + await updateTextDocumentWithNewLogEvents(formattedLogEvents, document, session.maxLines) + editorsToScroll.forEach(scrollTextEditorToBottom) + } + session.eventRate = eventRate(event.sessionUpdate) + session.isSampled = isSampled(event.sessionUpdate) + } + } + } catch (e) { + if (session.isAborted) { + // Expected case. User action cancelled stream (CodeLens, Close Editor, etc.). + // AbortSignal interrupts the LiveTail stream, causing error to be thrown here. + // Can assume that stopLiveTailSession() has already been called - AbortSignal is only + // exposed through that method. + getLogger().info(`LiveTail session stopped: ${uriToKey(session.uri)}`) + } else { + // Unexpected exception. + session.stopLiveTailSession() + throw ToolkitError.chain( + e, + `Unexpected on-stream exception while tailing session: ${session.uri.toString()}` + ) + } + } +} + +function formatLogEvent(logEvent: LiveTailSessionLogEvent): string { + if (!logEvent.timestamp || !logEvent.message) { + return '' + } + const timestamp = new Date(logEvent.timestamp).toLocaleTimeString('en', { + timeStyle: 'medium', + hour12: false, + timeZone: 'UTC', + }) + let line = timestamp.concat('\t', logEvent.message) + if (!line.endsWith('\n')) { + line = line.concat('\n') + } + return line +} + +// Auto scroll visible LiveTail session editors if the end-of-file is in view. +// This allows for newly added log events to stay in view. +function getTextEditorsToScroll(document: vscode.TextDocument): vscode.TextEditor[] { + return vscode.window.visibleTextEditors.filter((editor) => { + if (editor.document !== document) { + return false + } + return editor.visibleRanges[0].contains(new vscode.Position(document.lineCount - 1, 0)) + }) +} + +function scrollTextEditorToBottom(editor: vscode.TextEditor) { + const position = new vscode.Position(Math.max(editor.document.lineCount - 2, 0), 0) + editor.revealRange(new vscode.Range(position, position), vscode.TextEditorRevealType.Default) +} + +async function updateTextDocumentWithNewLogEvents( + formattedLogEvents: string[], + document: vscode.TextDocument, + maxLines: number +) { + const edit = new vscode.WorkspaceEdit() + formattedLogEvents.forEach((formattedLogEvent) => + edit.insert(document.uri, new vscode.Position(document.lineCount, 0), formattedLogEvent) + ) + if (document.lineCount + formattedLogEvents.length > maxLines) { + trimOldestLines(formattedLogEvents.length, maxLines, document, edit) + } + await vscode.workspace.applyEdit(edit) +} + +function trimOldestLines( + numNewLines: number, + maxLines: number, + document: vscode.TextDocument, + edit: vscode.WorkspaceEdit +) { + const numLinesToTrim = document.lineCount + numNewLines - maxLines + const startPosition = new vscode.Position(0, 0) + const endPosition = new vscode.Position(numLinesToTrim, 0) + const range = new vscode.Range(startPosition, endPosition) + edit.delete(document.uri, range) +} + +function isSampled(event: LiveTailSessionUpdate): boolean { + return event.sessionMetadata === undefined || event.sessionMetadata.sampled === undefined + ? false + : event.sessionMetadata.sampled +} + +function eventRate(event: LiveTailSessionUpdate): number { + return event.sessionResults === undefined ? 0 : event.sessionResults.length +} + +function hideShowStatusBarItemsOnActiveEditor( + session: LiveTailSession, + document: vscode.TextDocument +): vscode.Disposable { + return vscode.window.onDidChangeActiveTextEditor((editor) => { + session.showStatusBarItem(editor?.document === document) + }) +} + +/** + * The LiveTail session should be automatically closed if the user does not have the session's + * document in any Tab in their editor. + * + * `onDidCloseTextDocument` doesn't work for our case because the tailLogGroup command will keep the stream + * writing to the doc even when all its tabs/editors are closed, seemingly keeping the doc 'open'. + * Also there is no guarantee that this event fires when an editor tab is closed + * + * `onDidChangeVisibleTextEditors` returns editors that the user can see its contents. An editor that is open, but hidden + * from view, will not be returned. Meaning a Tab that is created (shown in top bar), but not open, will not be returned. Even if + * the tab isn't visible, we want to continue writing to the doc, and keep the session alive. + */ +function closeSessionWhenAllEditorsClosed( + session: LiveTailSession, + registry: LiveTailSessionRegistry, + document: vscode.TextDocument, + codeLensProvider: LiveTailCodeLensProvider +): vscode.Disposable { + return vscode.window.tabGroups.onDidChangeTabs((tabEvent) => { + const isOpen = isLiveTailSessionOpenInAnyTab(session) + if (!isOpen) { + closeSession(session.uri, registry, 'ClosedEditors', codeLensProvider) + void clearDocument(document) + } + }) +} + +function isLiveTailSessionOpenInAnyTab(liveTailSession: LiveTailSession) { + let isOpen = false + vscode.window.tabGroups.all.forEach(async (tabGroup) => { + tabGroup.tabs.forEach((tab) => { + if (tab.input instanceof vscode.TabInputText) { + if (liveTailSession.uri.toString() === tab.input.uri.toString()) { + isOpen = true + } + } + }) + }) + return isOpen +} diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/viewLogStream.ts b/packages/core/src/awsService/cloudWatchLogs/commands/viewLogStream.ts index a8ba6dbba76..270f64380ff 100644 --- a/packages/core/src/awsService/cloudWatchLogs/commands/viewLogStream.ts +++ b/packages/core/src/awsService/cloudWatchLogs/commands/viewLogStream.ts @@ -21,11 +21,11 @@ import { initLogData as initLogData, filterLogEventsFromUri, } from '../registry/logDataRegistry' -import { createURIFromArgs } from '../cloudWatchLogsUtils' import { prepareDocument, searchLogGroup } from './searchLogGroup' import { telemetry, Result } from '../../../shared/telemetry/telemetry' import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { formatLocalized } from '../../../shared/utilities/textUtilities' +import { cwlUriSchema } from '../cloudWatchLogsUtils' +import { formatLocalized } from '../../../shared/datetime' export async function viewLogStream(node: LogGroupNode, registry: LogDataRegistry): Promise { await telemetry.cloudwatchlogs_open.run(async (span) => { @@ -52,7 +52,7 @@ export async function viewLogStream(node: LogGroupNode, registry: LogDataRegistr limit: registry.configuration.get('limit', 10000), } - const uri = createURIFromArgs(logGroupInfo, parameters) + const uri = cwlUriSchema.form({ logGroupInfo: logGroupInfo, parameters: parameters }) const logData = initLogData(logGroupInfo, parameters, filterLogEventsFromUri) await prepareDocument(uri, logData, registry) }) diff --git a/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts new file mode 100644 index 00000000000..dde0ddbbe28 --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts @@ -0,0 +1,62 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants' +import { LiveTailSessionRegistry } from '../registry/liveTailSessionRegistry' +import { uriToKey } from '../cloudWatchLogsUtils' + +export class LiveTailCodeLensProvider implements vscode.CodeLensProvider { + private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter() + public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event + + public constructor(private readonly registry: LiveTailSessionRegistry) {} + + public provideCodeLenses( + document: vscode.TextDocument, + token: vscode.CancellationToken + ): vscode.ProviderResult { + const uri = document.uri + // if registry does not contain session, it is assumed to have been stopped, thus, hide lenses. + if (uri.scheme !== cloudwatchLogsLiveTailScheme || !this.registry.has(uriToKey(uri))) { + return [] + } + const codeLenses: vscode.CodeLens[] = [] + codeLenses.push(this.buildClearDocumentCodeLens(document)) + codeLenses.push(this.buildStopTailingCodeLens(document)) + return codeLenses + } + + public refresh() { + this._onDidChangeCodeLenses.fire() + } + + private buildClearDocumentCodeLens(document: vscode.TextDocument): vscode.CodeLens { + const range = this.getBottomOfDocumentRange(document) + const command: vscode.Command = { + title: 'Clear document', + command: 'aws.cwl.clearDocument', + arguments: [document], + } + return new vscode.CodeLens(range, command) + } + + private buildStopTailingCodeLens(document: vscode.TextDocument): vscode.CodeLens { + const range = this.getBottomOfDocumentRange(document) + const command: vscode.Command = { + title: 'Stop tailing', + command: 'aws.cwl.stopTailingLogGroup', + arguments: [document, 'codeLens'], + } + return new vscode.CodeLens(range, command) + } + + private getBottomOfDocumentRange(document: vscode.TextDocument): vscode.Range { + return new vscode.Range( + new vscode.Position(document.lineCount - 1, 0), + new vscode.Position(document.lineCount - 1, 0) + ) + } +} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/liveTailDocumentProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/liveTailDocumentProvider.ts new file mode 100644 index 00000000000..7d662890dcf --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/document/liveTailDocumentProvider.ts @@ -0,0 +1,13 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' + +export class LiveTailDocumentProvider implements vscode.TextDocumentContentProvider { + provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult { + // Content will be written to the document via handling a LiveTail response stream in the TailLogGroup command. + return '' + } +} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/logDataDocumentProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/logDataDocumentProvider.ts index 8eb390f7760..5925911893b 100644 --- a/packages/core/src/awsService/cloudWatchLogs/document/logDataDocumentProvider.ts +++ b/packages/core/src/awsService/cloudWatchLogs/document/logDataDocumentProvider.ts @@ -5,8 +5,8 @@ import * as vscode from 'vscode' import { CloudWatchLogsGroupInfo, LogDataRegistry, UriString } from '../registry/logDataRegistry' import { getLogger } from '../../../shared/logger' -import { isCwlUri } from '../cloudWatchLogsUtils' import { generateTextFromLogEvents, LineToLogStreamMap } from './textContent' +import { cwlUriSchema } from '../cloudWatchLogsUtils' export class LogDataDocumentProvider implements vscode.TextDocumentContentProvider { /** Resolves the correct {@link LineToLogStreamMap} instance for a given URI */ @@ -26,7 +26,7 @@ export class LogDataDocumentProvider implements vscode.TextDocumentContentProvid } public provideTextDocumentContent(uri: vscode.Uri): string { - if (!isCwlUri(uri)) { + if (!cwlUriSchema.isValid(uri)) { throw new Error(`Uri is not a CWL Uri, so no text can be provided: ${uri.toString()}`) } const events = this.registry.fetchCachedLogEvents(uri) diff --git a/packages/core/src/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.ts index 5c83ab929d9..9870ff009d2 100644 --- a/packages/core/src/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.ts +++ b/packages/core/src/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.ts @@ -6,12 +6,7 @@ import * as vscode from 'vscode' import { CLOUDWATCH_LOGS_SCHEME } from '../../../shared/constants' import { CloudWatchLogsGroupInfo, LogDataRegistry } from '../registry/logDataRegistry' -import { - CloudWatchLogsSettings, - createURIFromArgs, - isLogStreamUri, - parseCloudWatchLogsUri, -} from '../cloudWatchLogsUtils' +import { CloudWatchLogsSettings, cwlUriSchema, isLogStreamUri } from '../cloudWatchLogsUtils' import { LogDataDocumentProvider } from './logDataDocumentProvider' type IdWithLine = { streamId: string; lineNum: number } @@ -43,7 +38,7 @@ export class LogStreamCodeLensProvider implements vscode.CodeLensProvider { return [] } - const logGroupInfo = parseCloudWatchLogsUri(uri).logGroupInfo + const logGroupInfo = cwlUriSchema.parse(uri).logGroupInfo if (logGroupInfo.streamName) { // This means we have a stream file not a log search. @@ -64,7 +59,11 @@ export class LogStreamCodeLensProvider implements vscode.CodeLensProvider { createLogStreamCodeLens(logGroupInfo: CloudWatchLogsGroupInfo, idWithLine: IdWithLine): vscode.CodeLens { const settings = new CloudWatchLogsSettings() const limit = settings.get('limit', 1000) - const streamUri = createURIFromArgs({ ...logGroupInfo, streamName: idWithLine.streamId }, { limit: limit }) + const cwlArgs = { + logGroupInfo: { ...logGroupInfo, streamName: idWithLine.streamId }, + parameters: { limit: limit }, + } + const streamUri = cwlUriSchema.form(cwlArgs) const cmd: vscode.Command = { command: 'aws.loadLogStreamFile', arguments: [streamUri, this.registry], diff --git a/packages/core/src/awsService/cloudWatchLogs/document/textContent.ts b/packages/core/src/awsService/cloudWatchLogs/document/textContent.ts index ea20b57417c..7b415c586b1 100644 --- a/packages/core/src/awsService/cloudWatchLogs/document/textContent.ts +++ b/packages/core/src/awsService/cloudWatchLogs/document/textContent.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { formatDateTimestamp } from '../../../shared/datetime' import { CloudWatchLogsEvent, CloudWatchLogsGroupInfo } from '../registry/logDataRegistry' -import { formatDateTimestamp } from '../../../shared/utilities/textUtilities' export const timestampSpaceEquivalent = ' ' diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts new file mode 100644 index 00000000000..87eba4d4079 --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts @@ -0,0 +1,159 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import * as AWS from '@aws-sdk/types' +import { + CloudWatchLogsClient, + StartLiveTailCommand, + StartLiveTailResponseStream, +} from '@aws-sdk/client-cloudwatch-logs' +import { LogStreamFilterResponse } from '../wizard/liveTailLogStreamSubmenu' +import { CloudWatchLogsSettings } from '../cloudWatchLogsUtils' +import { globals, Settings, ToolkitError } from '../../../shared' +import { createLiveTailURIFromArgs } from './liveTailSessionRegistry' +import { getUserAgent } from '../../../shared/telemetry/util' +import { convertToTimeString } from '../../../shared/datetime' + +export type LiveTailSessionConfiguration = { + logGroupArn: string + logStreamFilter?: LogStreamFilterResponse + logEventFilterPattern?: string + region: string + awsCredentials: AWS.Credentials +} + +export type LiveTailSessionClient = { + cwlClient: CloudWatchLogsClient + abortController: AbortController +} + +export class LiveTailSession { + private liveTailClient: LiveTailSessionClient + private _logGroupArn: string + private logStreamFilter?: LogStreamFilterResponse + private logEventFilterPattern?: string + private _maxLines: number + private _uri: vscode.Uri + private statusBarItem: vscode.StatusBarItem + private startTime: number | undefined + private endTime: number | undefined + private _eventRate: number + private _isSampled: boolean + + // While session is running, used to update the StatusBar each half second. + private statusBarUpdateTimer: NodeJS.Timer | undefined + + static settings = new CloudWatchLogsSettings(Settings.instance) + + public constructor(configuration: LiveTailSessionConfiguration) { + this._logGroupArn = configuration.logGroupArn + this.logStreamFilter = configuration.logStreamFilter + this.logEventFilterPattern = configuration.logEventFilterPattern + this.liveTailClient = { + cwlClient: new CloudWatchLogsClient({ + credentials: configuration.awsCredentials, + region: configuration.region, + customUserAgent: getUserAgent(), + }), + abortController: new AbortController(), + } + this._maxLines = LiveTailSession.settings.get('limit', 10000) + this._uri = createLiveTailURIFromArgs(configuration) + this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 0) + this._eventRate = 0 + this._isSampled = false + } + + public get maxLines() { + return this._maxLines + } + + public get uri() { + return this._uri + } + + public get logGroupArn() { + return this._logGroupArn + } + + public set eventRate(rate: number) { + this._eventRate = rate + } + + public set isSampled(isSampled: boolean) { + this._isSampled = isSampled + } + + public async startLiveTailSession(): Promise> { + const commandOutput = await this.liveTailClient.cwlClient.send(this.buildStartLiveTailCommand(), { + abortSignal: this.liveTailClient.abortController.signal, + }) + if (!commandOutput.responseStream) { + throw new ToolkitError('LiveTail session response stream is undefined.') + } + this.startTime = globals.clock.Date.now() + this.endTime = undefined + this.statusBarUpdateTimer = globals.clock.setInterval(() => { + this.updateStatusBarItemText() + }, 500) + return commandOutput.responseStream + } + + public stopLiveTailSession() { + this.endTime = globals.clock.Date.now() + this.statusBarItem.dispose() + globals.clock.clearInterval(this.statusBarUpdateTimer) + this.liveTailClient.abortController.abort() + this.liveTailClient.cwlClient.destroy() + } + + public getLiveTailSessionDuration(): number { + // Never started + if (this.startTime === undefined) { + return 0 + } + // Currently running + if (this.endTime === undefined) { + return globals.clock.Date.now() - this.startTime + } + return this.endTime - this.startTime + } + + public buildStartLiveTailCommand(): StartLiveTailCommand { + let logStreamNamePrefix = undefined + let logStreamName = undefined + if (this.logStreamFilter) { + if (this.logStreamFilter.type === 'prefix') { + logStreamNamePrefix = this.logStreamFilter.filter + logStreamName = undefined + } else if (this.logStreamFilter.type === 'specific') { + logStreamName = this.logStreamFilter.filter + logStreamNamePrefix = undefined + } + } + + return new StartLiveTailCommand({ + logGroupIdentifiers: [this.logGroupArn], + logStreamNamePrefixes: logStreamNamePrefix ? [logStreamNamePrefix] : undefined, + logStreamNames: logStreamName ? [logStreamName] : undefined, + logEventFilterPattern: this.logEventFilterPattern ? this.logEventFilterPattern : undefined, + }) + } + + public showStatusBarItem(shouldShow: boolean) { + shouldShow ? this.statusBarItem.show() : this.statusBarItem.hide() + } + + public updateStatusBarItemText() { + const elapsedTime = this.getLiveTailSessionDuration() + const timeString = convertToTimeString(elapsedTime) + const sampledString = this._isSampled ? 'Yes' : 'No' + this.statusBarItem.text = `Tailing: ${timeString}, ${this._eventRate} events/sec, Sampled: ${sampledString}` + } + + public get isAborted() { + return this.liveTailClient.abortController.signal.aborted + } +} diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts new file mode 100644 index 00000000000..988725edc87 --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts @@ -0,0 +1,34 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants' +import { LiveTailSession, LiveTailSessionConfiguration } from './liveTailSession' + +export class LiveTailSessionRegistry extends Map { + static #instance: LiveTailSessionRegistry + + public static get instance() { + return (this.#instance ??= new this()) + } + + public constructor() { + super() + } +} + +export function createLiveTailURIFromArgs(sessionData: LiveTailSessionConfiguration): vscode.Uri { + let uriStr = `${cloudwatchLogsLiveTailScheme}:${sessionData.region}:${sessionData.logGroupArn}` + + if (sessionData.logStreamFilter) { + if (sessionData.logStreamFilter.type !== 'all') { + uriStr += `:${sessionData.logStreamFilter.type}:${sessionData.logStreamFilter.filter}` + } else { + uriStr += `:${sessionData.logStreamFilter.type}` + } + } + uriStr += sessionData.logEventFilterPattern ? `:${sessionData.logEventFilterPattern}` : '' + + return vscode.Uri.parse(uriStr) +} diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/logDataRegistry.ts b/packages/core/src/awsService/cloudWatchLogs/registry/logDataRegistry.ts index e8ae51558e5..88945d61bd2 100644 --- a/packages/core/src/awsService/cloudWatchLogs/registry/logDataRegistry.ts +++ b/packages/core/src/awsService/cloudWatchLogs/registry/logDataRegistry.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode' import { CloudWatchLogs } from 'aws-sdk' -import { CloudWatchLogsSettings, parseCloudWatchLogsUri, uriToKey, msgKey } from '../cloudWatchLogsUtils' +import { CloudWatchLogsSettings, uriToKey, msgKey, cwlUriSchema } from '../cloudWatchLogsUtils' import { DefaultCloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogsClient' import { waitTimeout } from '../../../shared/utilities/timeoutUtils' import { Messages } from '../../../shared/utilities/messages' @@ -190,7 +190,7 @@ export class LogDataRegistry { if (this.isRegistered(uri)) { throw new Error(`Already registered: ${uri.toString()}`) } - const data = parseCloudWatchLogsUri(uri) + const data = cwlUriSchema.parse(uri) this.setLogData(uri, initLogData(data.logGroupInfo, data.parameters, retrieveLogsFunction)) } @@ -281,6 +281,11 @@ export function initLogData( } } +export type CloudWatchLogsArgs = { + logGroupInfo: CloudWatchLogsGroupInfo + parameters: CloudWatchLogsParameters +} + export type CloudWatchLogsGroupInfo = { groupName: string regionName: string diff --git a/packages/core/src/awsService/cloudWatchLogs/timeFilterSubmenu.ts b/packages/core/src/awsService/cloudWatchLogs/timeFilterSubmenu.ts index 1f4dbc1d28a..0bb3cd9670d 100644 --- a/packages/core/src/awsService/cloudWatchLogs/timeFilterSubmenu.ts +++ b/packages/core/src/awsService/cloudWatchLogs/timeFilterSubmenu.ts @@ -38,7 +38,7 @@ export class TimeFilterSubmenu extends Prompter { private get recentTimeItems(): DataQuickPickItem[] { const options: DataQuickPickItem[] = [] - //appromixate 31 days as month length (better to overshoot) + // appromixate 31 days as month length (better to overshoot) options.push({ label: 'All time', data: 0, diff --git a/packages/core/src/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.ts b/packages/core/src/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.ts new file mode 100644 index 00000000000..c94259684bb --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.ts @@ -0,0 +1,163 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { Prompter, PromptResult } from '../../../shared/ui/prompter' +import { DefaultCloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogsClient' +import { createCommonButtons } from '../../../shared/ui/buttons' +import { createInputBox, InputBoxPrompter } from '../../../shared/ui/inputPrompter' +import { createQuickPick, DataQuickPickItem, QuickPickPrompter } from '../../../shared/ui/pickerPrompter' +import { pageableToCollection } from '../../../shared/utilities/collectionUtils' +import { CloudWatchLogs } from 'aws-sdk' +import { isValidResponse, StepEstimator } from '../../../shared/wizards/wizard' +import { isNonNullable } from '../../../shared/utilities/tsUtils' +import { + startLiveTailHelpUrl, + startLiveTailLogStreamNamesHelpUrl, + startLiveTailLogStreamPrefixHelpUrl, +} from '../../../shared/constants' + +export type LogStreamFilterType = 'menu' | 'prefix' | 'specific' | 'all' + +export interface LogStreamFilterResponse { + readonly filter?: string + readonly type: LogStreamFilterType +} + +export class LogStreamFilterSubmenu extends Prompter { + private logStreamPrefixRegEx = /^[^:*]*$/ + private currentState: LogStreamFilterType = 'menu' + private steps?: [current: number, total: number] + private region: string + private logGroupArn: string + public defaultPrompter: QuickPickPrompter = this.createMenuPrompter() + + public constructor(logGroupArn: string, region: string) { + super() + this.region = region + this.logGroupArn = logGroupArn + } + + public createMenuPrompter() { + const helpUri = startLiveTailHelpUrl + const prompter = createQuickPick(this.menuOptions, { + title: 'Include log events from...', + buttons: createCommonButtons(helpUri), + }) + return prompter + } + + private get menuOptions(): DataQuickPickItem[] { + const options: DataQuickPickItem[] = [] + options.push({ + label: 'All Log Streams', + data: 'all', + }) + options.push({ + label: 'Specific Log Stream', + data: 'specific', + }) + options.push({ + label: 'Log Streams matching prefix', + data: 'prefix', + }) + return options + } + + public createLogStreamPrefixBox(): InputBoxPrompter { + const helpUri = startLiveTailLogStreamPrefixHelpUrl + return createInputBox({ + title: 'Enter Log Stream prefix', + placeholder: 'log stream prefix (case sensitive; empty matches all)', + prompt: 'Only log events in Log Streams whose name starts with the supplied prefix will be included.', + validateInput: (input) => this.validateLogStreamPrefix(input), + buttons: createCommonButtons(helpUri), + }) + } + + public validateLogStreamPrefix(prefix: string) { + if (prefix.length > 512) { + return 'Log Stream prefix cannot be longer than 512 characters' + } + + if (!this.logStreamPrefixRegEx.test(prefix)) { + return 'Log Stream prefix must match pattern: [^:*]*' + } + } + + public createLogStreamSelector(): QuickPickPrompter { + const helpUri = startLiveTailLogStreamNamesHelpUrl + const client = new DefaultCloudWatchLogsClient(this.region) + const request: CloudWatchLogs.DescribeLogStreamsRequest = { + logGroupIdentifier: this.logGroupArn, + orderBy: 'LastEventTime', + descending: true, + } + const requester = (request: CloudWatchLogs.DescribeLogStreamsRequest) => client.describeLogStreams(request) + const collection = pageableToCollection(requester, request, 'nextToken', 'logStreams') + + const items = collection + .filter(isNonNullable) + .map((streams) => streams!.map((stream) => ({ data: stream.logStreamName!, label: stream.logStreamName! }))) + + return createQuickPick(items, { + title: 'Select Log Stream', + buttons: createCommonButtons(helpUri), + }) + } + + private switchState(newState: LogStreamFilterType) { + this.currentState = newState + } + + protected async promptUser(): Promise> { + while (true) { + switch (this.currentState) { + case 'menu': { + const prompter = this.createMenuPrompter() + this.steps && prompter.setSteps(this.steps[0], this.steps[1]) + + const resp = await prompter.prompt() + if (resp === 'prefix') { + this.switchState('prefix') + } else if (resp === 'specific') { + this.switchState('specific') + } else if (resp === 'all') { + return { filter: undefined, type: resp } + } else { + return undefined + } + + break + } + case 'prefix': { + const resp = await this.createLogStreamPrefixBox().prompt() + if (isValidResponse(resp)) { + return { filter: resp, type: 'prefix' } + } + this.switchState('menu') + break + } + case 'specific': { + const resp = await this.createLogStreamSelector().prompt() + if (isValidResponse(resp)) { + return { filter: resp, type: 'specific' } + } + this.switchState('menu') + break + } + } + } + } + + public setSteps(current: number, total: number): void { + this.steps = [current, total] + } + + // Unused + public get recentItem(): any { + return + } + public set recentItem(response: any) {} + public setStepEstimator(estimator: StepEstimator): void {} +} diff --git a/packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts b/packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts new file mode 100644 index 00000000000..025820df792 --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts @@ -0,0 +1,109 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as nls from 'vscode-nls' +import { globals, ToolkitError } from '../../../shared' +import { DefaultCloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogsClient' +import { cwlFilterPatternHelpUrl } from '../../../shared/constants' +import { createBackButton, createExitButton, createHelpButton } from '../../../shared/ui/buttons' +import { RegionSubmenu, RegionSubmenuResponse } from '../../../shared/ui/common/regionSubmenu' +import { createInputBox } from '../../../shared/ui/inputPrompter' +import { DataQuickPickItem } from '../../../shared/ui/pickerPrompter' +import { Wizard } from '../../../shared/wizards/wizard' +import { CloudWatchLogsGroupInfo } from '../registry/logDataRegistry' +import { LogStreamFilterResponse, LogStreamFilterSubmenu } from './liveTailLogStreamSubmenu' + +const localize = nls.loadMessageBundle() + +export interface TailLogGroupWizardResponse { + regionLogGroupSubmenuResponse: RegionSubmenuResponse + logStreamFilter: LogStreamFilterResponse + filterPattern: string +} + +export class TailLogGroupWizard extends Wizard { + public constructor(logGroupInfo?: CloudWatchLogsGroupInfo) { + super({ + initState: { + regionLogGroupSubmenuResponse: logGroupInfo + ? { + data: buildLogGroupArn(logGroupInfo.groupName, logGroupInfo.regionName), + region: logGroupInfo.regionName, + } + : undefined, + }, + }) + this.form.regionLogGroupSubmenuResponse.bindPrompter(createRegionLogGroupSubmenu) + this.form.logStreamFilter.bindPrompter((state) => { + if (!state.regionLogGroupSubmenuResponse?.data) { + throw new ToolkitError('Log Group name is null') + } + return new LogStreamFilterSubmenu( + state.regionLogGroupSubmenuResponse.data, + state.regionLogGroupSubmenuResponse.region + ) + }) + this.form.filterPattern.bindPrompter((state) => createFilterPatternPrompter()) + } +} + +export function createRegionLogGroupSubmenu(): RegionSubmenu { + return new RegionSubmenu( + getLogGroupQuickPickOptions, + { + title: localize('AWS.cwl.tailLogGroup.logGroupPromptTitle', 'Select Log Group to tail'), + buttons: [createExitButton()], + }, + { title: localize('AWS.cwl.tailLogGroup.regionPromptTitle', 'Select Region for Log Group') }, + 'Log Groups' + ) +} + +async function getLogGroupQuickPickOptions(regionCode: string): Promise[]> { + const client = new DefaultCloudWatchLogsClient(regionCode) + const logGroups = client.describeLogGroups() + + const logGroupsOptions: DataQuickPickItem[] = [] + + for await (const logGroupObject of logGroups) { + if (!logGroupObject.arn || !logGroupObject.logGroupName) { + throw new ToolkitError('Log Group name or arn is undefined') + } + + logGroupsOptions.push({ + label: logGroupObject.logGroupName, + data: formatLogGroupArn(logGroupObject.arn), + }) + } + + return logGroupsOptions +} + +export function buildLogGroupArn(logGroupName: string, region: string): string { + if (logGroupName.startsWith('arn:')) { + return logGroupName + } + const awsAccountId = globals.awsContext.getCredentialAccountId() + if (awsAccountId === undefined) { + throw new ToolkitError( + `Failed to construct Arn for Log Group because awsAccountId is undefined. Log Group: ${logGroupName}` + ) + } + return `arn:aws:logs:${region}:${awsAccountId}:log-group:${logGroupName}` +} + +function formatLogGroupArn(logGroupArn: string): string { + return logGroupArn.endsWith(':*') ? logGroupArn.substring(0, logGroupArn.length - 2) : logGroupArn +} + +export function createFilterPatternPrompter() { + const helpUri = cwlFilterPatternHelpUrl + return createInputBox({ + title: 'Provide log event filter pattern', + placeholder: 'filter pattern (case sensitive; empty matches all)', + prompt: 'Optional filter to include only log events that match the supplied pattern.', + buttons: [createHelpButton(helpUri), createBackButton(), createExitButton()], + }) +} diff --git a/packages/core/src/awsService/ec2/activation.ts b/packages/core/src/awsService/ec2/activation.ts index 550a0bbf47c..f8881af8347 100644 --- a/packages/core/src/awsService/ec2/activation.ts +++ b/packages/core/src/awsService/ec2/activation.ts @@ -2,10 +2,11 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ +import * as vscode from 'vscode' import { ExtContext } from '../../shared/extensions' import { Commands } from '../../shared/vscode/commands2' import { telemetry } from '../../shared/telemetry/telemetry' -import { Ec2InstanceNode } from './explorer/ec2InstanceNode' +import { Ec2InstanceNode, tryRefreshNode } from './explorer/ec2InstanceNode' import { copyTextCommand } from '../../awsexplorer/commands/copyText' import { Ec2Node } from './explorer/ec2ParentNode' import { @@ -14,32 +15,46 @@ import { rebootInstance, startInstance, stopInstance, - refreshExplorer, linkToLaunchInstance, + openLogDocument, } from './commands' +import { Ec2ConnecterMap } from './connectionManagerMap' +import { ec2LogsScheme } from '../../shared/constants' +import { Ec2LogDocumentProvider } from './ec2LogDocumentProvider' + +const connectionManagers = new Ec2ConnecterMap() export async function activate(ctx: ExtContext): Promise { + ctx.extensionContext.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider(ec2LogsScheme, new Ec2LogDocumentProvider()) + ) ctx.extensionContext.subscriptions.push( Commands.register('aws.ec2.openTerminal', async (node?: Ec2InstanceNode) => { await telemetry.ec2_connectToInstance.run(async (span) => { span.record({ ec2ConnectionType: 'ssm' }) - await openTerminal(node) + await openTerminal(connectionManagers, node) }) }), Commands.register('aws.ec2.copyInstanceId', async (node: Ec2InstanceNode) => { await copyTextCommand(node, 'id') }), + Commands.register('aws.ec2.viewLogs', async (node?: Ec2InstanceNode) => { + await openLogDocument(node) + }), Commands.register('aws.ec2.openRemoteConnection', async (node?: Ec2Node) => { - await openRemoteConnection(node) + await telemetry.ec2_connectToInstance.run(async (span) => { + span.record({ ec2ConnectionType: 'remoteWorkspace' }) + await openRemoteConnection(connectionManagers, node) + }) }), Commands.register('aws.ec2.startInstance', async (node?: Ec2Node) => { await telemetry.ec2_changeState.run(async (span) => { span.record({ ec2InstanceState: 'start' }) await startInstance(node) - refreshExplorer(node) + await tryRefreshNode(node) }) }), @@ -47,7 +62,7 @@ export async function activate(ctx: ExtContext): Promise { await telemetry.ec2_changeState.run(async (span) => { span.record({ ec2InstanceState: 'stop' }) await stopInstance(node) - refreshExplorer(node) + await tryRefreshNode(node) }) }), @@ -55,7 +70,7 @@ export async function activate(ctx: ExtContext): Promise { await telemetry.ec2_changeState.run(async (span) => { span.record({ ec2InstanceState: 'reboot' }) await rebootInstance(node) - refreshExplorer(node) + await tryRefreshNode(node) }) }), @@ -66,3 +81,7 @@ export async function activate(ctx: ExtContext): Promise { }) ) } + +export async function deactivate(): Promise { + connectionManagers.forEach(async (manager) => await manager.dispose()) +} diff --git a/packages/core/src/awsService/ec2/commands.ts b/packages/core/src/awsService/ec2/commands.ts index f09ea0ea10c..4f06f7876eb 100644 --- a/packages/core/src/awsService/ec2/commands.ts +++ b/packages/core/src/awsService/ec2/commands.ts @@ -2,37 +2,27 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ - import { Ec2InstanceNode } from './explorer/ec2InstanceNode' import { Ec2Node } from './explorer/ec2ParentNode' -import { Ec2ConnectionManager } from './model' -import { Ec2Prompter, instanceFilter, Ec2Selection } from './prompter' import { SafeEc2Instance, Ec2Client } from '../../shared/clients/ec2Client' import { copyToClipboard } from '../../shared/utilities/messages' -import { getLogger } from '../../shared/logger' +import { ec2LogSchema } from './ec2LogDocumentProvider' import { getAwsConsoleUrl } from '../../shared/awsConsole' import { showRegionPrompter } from '../../auth/utils' import { openUrl } from '../../shared/utilities/vsCodeUtils' +import { showFile } from '../../shared/utilities/textDocumentUtilities' +import { Ec2ConnecterMap } from './connectionManagerMap' +import { Ec2Prompter, Ec2Selection, instanceFilter } from './prompter' -export function refreshExplorer(node?: Ec2Node) { - if (node) { - const n = node instanceof Ec2InstanceNode ? node.parent : node - n.refreshNode().catch((e) => { - getLogger().error('refreshNode failed: %s', (e as Error).message) - }) - } -} - -export async function openTerminal(node?: Ec2Node) { +export async function openTerminal(connectionManagers: Ec2ConnecterMap, node?: Ec2Node) { const selection = await getSelection(node) - - const connectionManager = new Ec2ConnectionManager(selection.region) + const connectionManager = connectionManagers.getOrInit(selection.region) await connectionManager.attemptToOpenEc2Terminal(selection) } -export async function openRemoteConnection(node?: Ec2Node) { +export async function openRemoteConnection(connectionManagers: Ec2ConnecterMap, node?: Ec2Node) { const selection = await getSelection(node) - const connectionManager = new Ec2ConnectionManager(selection.region) + const connectionManager = connectionManagers.getOrInit(selection.region) await connectionManager.tryOpenRemoteConnection(selection) } @@ -71,3 +61,7 @@ async function getSelection(node?: Ec2Node, filter?: instanceFilter): Promise { await copyToClipboard(instanceId, 'Id') } + +export async function openLogDocument(node?: Ec2InstanceNode): Promise { + return await showFile(ec2LogSchema.form(await getSelection(node))) +} diff --git a/packages/core/src/awsService/ec2/connectionManagerMap.ts b/packages/core/src/awsService/ec2/connectionManagerMap.ts new file mode 100644 index 00000000000..add39f05bea --- /dev/null +++ b/packages/core/src/awsService/ec2/connectionManagerMap.ts @@ -0,0 +1,26 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getLogger } from '../../shared' +import { Ec2Connecter } from './model' + +export class Ec2ConnecterMap extends Map { + private static warnSize: number = 25 + + public getOrInit(regionCode: string) { + return this.has(regionCode) ? this.get(regionCode)! : this.initManager(regionCode) + } + + private initManager(regionCode: string): Ec2Connecter { + if (this.size >= Ec2ConnecterMap.warnSize) { + getLogger().warn( + `Connection manager exceeded threshold of ${Ec2ConnecterMap.warnSize} with ${this.size} active connections` + ) + } + const newConnectionManager = new Ec2Connecter(regionCode) + this.set(regionCode, newConnectionManager) + return newConnectionManager + } +} diff --git a/packages/core/src/awsService/ec2/ec2LogDocumentProvider.ts b/packages/core/src/awsService/ec2/ec2LogDocumentProvider.ts new file mode 100644 index 00000000000..1c8e68f0e02 --- /dev/null +++ b/packages/core/src/awsService/ec2/ec2LogDocumentProvider.ts @@ -0,0 +1,42 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import { Ec2Selection } from './prompter' +import { Ec2Client } from '../../shared/clients/ec2Client' +import { ec2LogsScheme } from '../../shared/constants' +import { UriSchema } from '../../shared/utilities/uriUtils' + +export class Ec2LogDocumentProvider implements vscode.TextDocumentContentProvider { + public constructor() {} + + public async provideTextDocumentContent(uri: vscode.Uri): Promise { + if (!ec2LogSchema.isValid(uri)) { + throw new Error(`Invalid EC2 Logs URI: ${uri.toString()}`) + } + const ec2Selection = ec2LogSchema.parse(uri) + const ec2Client = new Ec2Client(ec2Selection.region) + const consoleOutput = await ec2Client.getConsoleOutput(ec2Selection.instanceId, false) + return consoleOutput.Output + } +} + +export const ec2LogSchema = new UriSchema(parseEc2Uri, formEc2Uri) + +function parseEc2Uri(uri: vscode.Uri): Ec2Selection { + const parts = uri.path.split(':') + + if (uri.scheme !== ec2LogsScheme) { + throw new Error(`URI ${uri} is not parseable for EC2 Logs`) + } + + return { + instanceId: parts[1], + region: parts[0], + } +} + +function formEc2Uri(selection: Ec2Selection): vscode.Uri { + return vscode.Uri.parse(`${ec2LogsScheme}:${selection.region}:${selection.instanceId}`) +} diff --git a/packages/core/src/awsService/ec2/explorer/ec2InstanceNode.ts b/packages/core/src/awsService/ec2/explorer/ec2InstanceNode.ts index ded19d5059a..d00fe55e9c2 100644 --- a/packages/core/src/awsService/ec2/explorer/ec2InstanceNode.ts +++ b/packages/core/src/awsService/ec2/explorer/ec2InstanceNode.ts @@ -10,8 +10,9 @@ import { SafeEc2Instance } from '../../../shared/clients/ec2Client' import globals from '../../../shared/extensionGlobals' import { getIconCode } from '../utils' import { Ec2Selection } from '../prompter' -import { Ec2ParentNode } from './ec2ParentNode' +import { Ec2Node, Ec2ParentNode } from './ec2ParentNode' import { EC2 } from 'aws-sdk' +import { getLogger } from '../../../shared' export const Ec2InstanceRunningContext = 'awsEc2RunningNode' export const Ec2InstanceStoppedContext = 'awsEc2StoppedNode' @@ -29,6 +30,7 @@ export class Ec2InstanceNode extends AWSTreeNodeBase implements AWSResourceNode public readonly instance: SafeEc2Instance ) { super('') + this.parent.addChild(this) this.updateInstance(instance) this.id = this.InstanceId } @@ -41,7 +43,7 @@ export class Ec2InstanceNode extends AWSTreeNodeBase implements AWSResourceNode this.tooltip = `${this.name}\n${this.InstanceId}\n${this.instance.LastSeenStatus}\n${this.arn}` if (this.isPending()) { - this.parent.pollingSet.start(this.InstanceId) + this.parent.trackPendingNode(this.InstanceId) } } @@ -100,3 +102,14 @@ export class Ec2InstanceNode extends AWSTreeNodeBase implements AWSResourceNode await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) } } + +export async function tryRefreshNode(node?: Ec2Node) { + if (node) { + const n = node instanceof Ec2InstanceNode ? node.parent : node + try { + await n.refreshNode() + } catch (e) { + getLogger().error('refreshNode failed: %s', (e as Error).message) + } + } +} diff --git a/packages/core/src/awsService/ec2/explorer/ec2ParentNode.ts b/packages/core/src/awsService/ec2/explorer/ec2ParentNode.ts index b01a310323f..854e6eacd1c 100644 --- a/packages/core/src/awsService/ec2/explorer/ec2ParentNode.ts +++ b/packages/core/src/awsService/ec2/explorer/ec2ParentNode.ts @@ -41,6 +41,13 @@ export class Ec2ParentNode extends AWSTreeNodeBase { }) } + public trackPendingNode(instanceId: string) { + if (!this.ec2InstanceNodes.has(instanceId)) { + throw new Error(`Attempt to track ec2 node ${instanceId} that isn't a child`) + } + this.pollingSet.start(instanceId) + } + public async updateChildren(): Promise { const ec2Instances = await (await this.ec2Client.getInstances()).toMap((instance) => instance.InstanceId) updateInPlace( @@ -52,21 +59,38 @@ export class Ec2ParentNode extends AWSTreeNodeBase { ) } - private updatePendingNodes() { - this.pollingSet.forEach(async (instanceId) => { - const childNode = this.ec2InstanceNodes.get(instanceId)! - await childNode.updateStatus() - if (!childNode.isPending()) { - this.pollingSet.delete(instanceId) - await childNode.refreshNode() - } - }) + public getInstanceNode(instanceId: string): Ec2InstanceNode { + const childNode = this.ec2InstanceNodes.get(instanceId) + if (childNode) { + return childNode + } else { + throw new Error(`Node with id ${instanceId} from polling set not found`) + } + } + + private async updatePendingNodes() { + for (const instanceId of this.pollingSet.values()) { + const childNode = this.getInstanceNode(instanceId) + await this.updatePendingNode(childNode) + } + } + + private async updatePendingNode(node: Ec2InstanceNode) { + await node.updateStatus() + if (!node.isPending()) { + this.pollingSet.delete(node.InstanceId) + await node.refreshNode() + } } public async clearChildren() { this.ec2InstanceNodes = new Map() } + public addChild(node: Ec2InstanceNode) { + this.ec2InstanceNodes.set(node.InstanceId, node) + } + public async refreshNode(): Promise { await this.clearChildren() await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) diff --git a/packages/core/src/awsService/ec2/model.ts b/packages/core/src/awsService/ec2/model.ts index 021ad623dd2..9a5e13feaef 100644 --- a/packages/core/src/awsService/ec2/model.ts +++ b/packages/core/src/awsService/ec2/model.ts @@ -3,9 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ import * as vscode from 'vscode' -import * as path from 'path' import { Session } from 'aws-sdk/clients/ssm' -import { IAM, SSM } from 'aws-sdk' +import { EC2, IAM, SSM } from 'aws-sdk' import { Ec2Selection } from './prompter' import { getOrInstallCli } from '../../shared/utilities/cliUtils' import { isCloud9 } from '../../shared/extensionUtilities' @@ -14,6 +13,7 @@ import { SsmClient } from '../../shared/clients/ssmClient' import { Ec2Client } from '../../shared/clients/ec2Client' import { VscodeRemoteConnection, + createBoundProcess, ensureDependencies, getDeniedSsmActions, openRemoteTerminal, @@ -21,26 +21,40 @@ import { } from '../../shared/remoteSession' import { DefaultIamClient } from '../../shared/clients/iamClient' import { ErrorInformation } from '../../shared/errors' -import { sshAgentSocketVariable, startSshAgent, startVscodeRemote } from '../../shared/extensions/ssh' -import { createBoundProcess } from '../../codecatalyst/model' +import { + sshAgentSocketVariable, + SshError, + startSshAgent, + startVscodeRemote, + testSshConnection, +} from '../../shared/extensions/ssh' import { getLogger } from '../../shared/logger/logger' import { CancellationError, Timeout } from '../../shared/utilities/timeoutUtils' import { showMessageWithCancel } from '../../shared/utilities/messages' -import { SshConfig, sshLogFileLocation } from '../../shared/sshConfig' +import { SshConfig } from '../../shared/sshConfig' import { SshKeyPair } from './sshKeyPair' -import globals from '../../shared/extensionGlobals' +import { Ec2SessionTracker } from './remoteSessionManager' +import { getEc2SsmEnv } from './utils' export type Ec2ConnectErrorCode = 'EC2SSMStatus' | 'EC2SSMPermission' | 'EC2SSMConnect' | 'EC2SSMAgentStatus' -interface Ec2RemoteEnv extends VscodeRemoteConnection { +export interface Ec2RemoteEnv extends VscodeRemoteConnection { selection: Ec2Selection keyPair: SshKeyPair + ssmSession: SSM.StartSessionResponse +} + +export type Ec2OS = 'Amazon Linux' | 'Ubuntu' | 'macOS' +interface RemoteUser { + os: Ec2OS + name: string } -export class Ec2ConnectionManager { +export class Ec2Connecter implements vscode.Disposable { protected ssmClient: SsmClient protected ec2Client: Ec2Client protected iamClient: DefaultIamClient + protected sessionManager: Ec2SessionTracker private policyDocumentationUri = vscode.Uri.parse( 'https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-instance-profile.html' @@ -54,6 +68,7 @@ export class Ec2ConnectionManager { this.ssmClient = this.createSsmSdkClient() this.ec2Client = this.createEc2SdkClient() this.iamClient = this.createIamSdkClient() + this.sessionManager = new Ec2SessionTracker(regionCode, this.ssmClient) } protected createSsmSdkClient(): SsmClient { @@ -68,6 +83,18 @@ export class Ec2ConnectionManager { return new DefaultIamClient(this.regionCode) } + public async addActiveSession(sessionId: SSM.SessionId, instanceId: EC2.InstanceId): Promise { + await this.sessionManager.addSession(instanceId, sessionId) + } + + public async dispose(): Promise { + await this.sessionManager.dispose() + } + + public isConnectedTo(instanceId: string): boolean { + return this.sessionManager.isConnectedTo(instanceId) + } + public async getAttachedIamRole(instanceId: string): Promise { const IamInstanceProfile = await this.ec2Client.getAttachedIamInstanceProfile(instanceId) if (IamInstanceProfile && IamInstanceProfile.Arn) { @@ -134,13 +161,6 @@ export class Ec2ConnectionManager { } } - public throwGeneralConnectionError(selection: Ec2Selection, error: Error) { - this.throwConnectionError('Unable to connect to target instance. ', selection, { - code: 'EC2SSMConnect', - cause: error, - }) - } - public async checkForStartSessionError(selection: Ec2Selection): Promise { await this.checkForInstanceStatusError(selection) @@ -169,7 +189,7 @@ export class Ec2ConnectionManager { const response = await this.ssmClient.startSession(selection.instanceId) await this.openSessionInTerminal(response, selection) } catch (err: unknown) { - this.throwGeneralConnectionError(selection, err as Error) + this.throwConnectionError('', selection, err as Error) } } @@ -178,26 +198,53 @@ export class Ec2ConnectionManager { const remoteUser = await this.getRemoteUser(selection.instanceId) const remoteEnv = await this.prepareEc2RemoteEnvWithProgress(selection, remoteUser) - + const testSession = await this.ssmClient.startSession(selection.instanceId, 'AWS-StartSSHSession') try { - await startVscodeRemote(remoteEnv.SessionProcess, remoteEnv.hostname, '/', remoteEnv.vscPath, remoteUser) + await testSshConnection( + remoteEnv.SessionProcess, + remoteEnv.hostname, + remoteEnv.sshPath, + remoteUser.name, + testSession + ) + await startVscodeRemote( + remoteEnv.SessionProcess, + remoteEnv.hostname, + '/', + remoteEnv.vscPath, + remoteUser.name + ) } catch (err) { - this.throwGeneralConnectionError(selection, err as Error) + const message = err instanceof SshError ? 'Testing SSH connection to instance failed' : '' + this.throwConnectionError(message, selection, err as Error) + } finally { + await this.ssmClient.terminateSession(testSession) } } - public async prepareEc2RemoteEnvWithProgress(selection: Ec2Selection, remoteUser: string): Promise { + + public async prepareEc2RemoteEnvWithProgress( + selection: Ec2Selection, + remoteUser: RemoteUser + ): Promise { const timeout = new Timeout(60000) await showMessageWithCancel('AWS: Opening remote connection...', timeout) const remoteEnv = await this.prepareEc2RemoteEnv(selection, remoteUser).finally(() => timeout.cancel()) return remoteEnv } - public async prepareEc2RemoteEnv(selection: Ec2Selection, remoteUser: string): Promise { + private async startSSMSession(instanceId: string): Promise { + const ssmSession = await this.ssmClient.startSession(instanceId, 'AWS-StartSSHSession') + await this.addActiveSession(instanceId, ssmSession.SessionId!) + return ssmSession + } + + public async prepareEc2RemoteEnv(selection: Ec2Selection, remoteUser: RemoteUser): Promise { const logger = this.configureRemoteConnectionLogger(selection.instanceId) const { ssm, vsc, ssh } = (await ensureDependencies()).unwrap() const keyPair = await this.configureSshKeys(selection, remoteUser) - const hostNamePrefix = 'aws-ec2-' - const sshConfig = new SshConfig(ssh, hostNamePrefix, 'ec2_connect', keyPair.getPrivateKeyPath()) + const hostnamePrefix = 'aws-ec2-' + const hostname = `${hostnamePrefix}${selection.instanceId}` + const sshConfig = new SshConfig(ssh, hostnamePrefix, 'ec2_connect', keyPair.getPrivateKeyPath()) const config = await sshConfig.ensureValid() if (config.isErr()) { @@ -206,8 +253,12 @@ export class Ec2ConnectionManager { throw err } - const session = await this.ssmClient.startSession(selection.instanceId, 'AWS-StartSSHSession') - const vars = getEc2SsmEnv(selection, ssm, session) + + const ssmSession = await this.startSSMSession(selection.instanceId) + + const vars = getEc2SsmEnv(selection, ssm, ssmSession) + getLogger().debug(`ec2: connect script logs at ${vars.LOG_FILE_LOCATION}`) + const envProvider = async () => { return { [sshAgentSocketVariable]: await startSshAgent(), ...vars } } @@ -218,13 +269,14 @@ export class Ec2ConnectionManager { }) return { - hostname: `${hostNamePrefix}${selection.instanceId}`, + hostname, envProvider, sshPath: ssh, vscPath: vsc, SessionProcess, selection, keyPair, + ssmSession, } } @@ -234,53 +286,78 @@ export class Ec2ConnectionManager { return logger } - public async configureSshKeys(selection: Ec2Selection, remoteUser: string): Promise { - const keyPath = path.join(globals.context.globalStorageUri.fsPath, `aws-ec2-key`) - const keyPair = await SshKeyPair.getSshKeyPair(keyPath, 30000) + public async configureSshKeys(selection: Ec2Selection, remoteUser: RemoteUser): Promise { + const keyPair = await SshKeyPair.getSshKeyPair(`aws-ec2-key`, 30000) await this.sendSshKeyToInstance(selection, keyPair, remoteUser) return keyPair } + /** Removes old key(s) that we added to the remote ~/.ssh/authorized_keys file. */ + public async tryCleanKeys( + instanceId: string, + hintComment: string, + hostOS: Ec2OS, + remoteAuthorizedKeysPath: string + ) { + try { + const deleteExistingKeyCommand = getRemoveLinesCommand(hintComment, hostOS, remoteAuthorizedKeysPath) + await this.sendCommandAndWait(instanceId, deleteExistingKeyCommand) + } catch (e) { + getLogger().warn(`ec2: failed to clean keys: %O`, e) + } + } + + private async sendCommandAndWait(instanceId: string, command: string) { + return await this.ssmClient.sendCommandAndWait(instanceId, 'AWS-RunShellScript', { + commands: [command], + }) + } + public async sendSshKeyToInstance( selection: Ec2Selection, sshKeyPair: SshKeyPair, - remoteUser: string + remoteUser: RemoteUser ): Promise { const sshPubKey = await sshKeyPair.getPublicKey() + const hintComment = '#AWSToolkitForVSCode' - const remoteAuthorizedKeysPaths = `/home/${remoteUser}/.ssh/authorized_keys` - const command = `echo "${sshPubKey}" > ${remoteAuthorizedKeysPaths}` - const documentName = 'AWS-RunShellScript' + const remoteAuthorizedKeysPath = `/home/${remoteUser.name}/.ssh/authorized_keys` - await this.ssmClient.sendCommandAndWait(selection.instanceId, documentName, { - commands: [command], - }) + const appendStr = (s: string) => `echo "${s}" >> ${remoteAuthorizedKeysPath}` + const writeKeyCommand = appendStr([sshPubKey.replace('\n', ''), hintComment].join(' ')) + + await this.tryCleanKeys(selection.instanceId, hintComment, remoteUser.os, remoteAuthorizedKeysPath) + await this.sendCommandAndWait(selection.instanceId, writeKeyCommand) } - public async getRemoteUser(instanceId: string) { - const osName = await this.ssmClient.getTargetPlatformName(instanceId) - if (osName === 'Amazon Linux') { - return 'ec2-user' + public async getRemoteUser(instanceId: string): Promise { + const os = await this.ssmClient.getTargetPlatformName(instanceId) + if (os === 'Amazon Linux') { + return { name: 'ec2-user', os } } - if (osName === 'Ubuntu') { - return 'ubuntu' + if (os === 'Ubuntu') { + return { name: 'ubuntu', os } } - throw new ToolkitError(`Unrecognized OS name ${osName} on instance ${instanceId}`, { code: 'UnknownEc2OS' }) + throw new ToolkitError(`Unrecognized OS name ${os} on instance ${instanceId}`, { code: 'UnknownEc2OS' }) } } -function getEc2SsmEnv(selection: Ec2Selection, ssmPath: string, session: SSM.StartSessionResponse): NodeJS.ProcessEnv { - return Object.assign( - { - AWS_REGION: selection.region, - AWS_SSM_CLI: ssmPath, - LOG_FILE_LOCATION: sshLogFileLocation('ec2', selection.instanceId), - STREAM_URL: session.StreamUrl, - SESSION_ID: session.SessionId, - TOKEN: session.TokenValue, - }, - process.env - ) +/** + * Generate bash command (as string) to remove lines containing `pattern`. + * @param pattern pattern for deleted lines. + * @param filepath filepath (as string) to target with the command. + * @returns bash command to remove lines from file. + */ +export function getRemoveLinesCommand(pattern: string, hostOS: Ec2OS, filepath: string): string { + if (pattern.includes('/')) { + throw new ToolkitError(`ec2: cannot match pattern containing '/', given: ${pattern}`) + } + // Linux allows not passing extension to -i, whereas macOS requires zero length extension. + return `sed -i${isLinux(hostOS) ? '' : " ''"} /${pattern}/d ${filepath}` +} + +function isLinux(os: Ec2OS): boolean { + return os === 'Amazon Linux' || os === 'Ubuntu' } diff --git a/packages/core/src/awsService/ec2/prompter.ts b/packages/core/src/awsService/ec2/prompter.ts index 6bbc301e515..638458942e2 100644 --- a/packages/core/src/awsService/ec2/prompter.ts +++ b/packages/core/src/awsService/ec2/prompter.ts @@ -10,6 +10,8 @@ import { isValidResponse } from '../../shared/wizards/wizard' import { CancellationError } from '../../shared/utilities/timeoutUtils' import { AsyncCollection } from '../../shared/utilities/asyncCollection' import { getIconCode } from './utils' +import { Ec2Node } from './explorer/ec2ParentNode' +import { Ec2InstanceNode } from './explorer/ec2InstanceNode' export type instanceFilter = (instance: SafeEc2Instance) => boolean export interface Ec2Selection { @@ -72,3 +74,9 @@ export class Ec2Prompter { ) } } + +export async function getSelection(node?: Ec2Node, filter?: instanceFilter): Promise { + const prompter = new Ec2Prompter(filter) + const selection = node && node instanceof Ec2InstanceNode ? node.toSelection() : await prompter.promptUser() + return selection +} diff --git a/packages/core/src/awsService/ec2/remoteSessionManager.ts b/packages/core/src/awsService/ec2/remoteSessionManager.ts new file mode 100644 index 00000000000..4c1843aabdb --- /dev/null +++ b/packages/core/src/awsService/ec2/remoteSessionManager.ts @@ -0,0 +1,40 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EC2, SSM } from 'aws-sdk' +import { SsmClient } from '../../shared/clients/ssmClient' +import { Disposable } from 'vscode' + +export class Ec2SessionTracker extends Map implements Disposable { + public constructor( + readonly regionCode: string, + protected ssmClient: SsmClient + ) { + super() + } + + public async addSession(instanceId: EC2.InstanceId, sessionId: SSM.SessionId): Promise { + if (this.isConnectedTo(instanceId)) { + const existingSessionId = this.get(instanceId)! + await this.ssmClient.terminateSessionFromId(existingSessionId) + this.set(instanceId, sessionId) + } else { + this.set(instanceId, sessionId) + } + } + + private async disconnectEnv(instanceId: EC2.InstanceId): Promise { + await this.ssmClient.terminateSessionFromId(this.get(instanceId)!) + this.delete(instanceId) + } + + public async dispose(): Promise { + this.forEach(async (_sessionId, instanceId) => await this.disconnectEnv(instanceId)) + } + + public isConnectedTo(instanceId: EC2.InstanceId): boolean { + return this.has(instanceId) + } +} diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts index 1b7a461655d..369c14964c7 100644 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ b/packages/core/src/awsService/ec2/sshKeyPair.ts @@ -2,25 +2,26 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -import { fs } from '../../shared' -import { chmodSync } from 'fs' +import os from 'os' +import { fs, globals } from '../../shared' import { ToolkitError } from '../../shared/errors' import { tryRun } from '../../shared/utilities/pathFind' import { Timeout } from '../../shared/utilities/timeoutUtils' import { findAsync } from '../../shared/utilities/collectionUtils' +import { RunParameterContext } from '../../shared/utilities/processUtils' +import path from 'path' type sshKeyType = 'rsa' | 'ed25519' export class SshKeyPair { private publicKeyPath: string private lifeTimeout: Timeout - private deleted: boolean = false private constructor( - private keyPath: string, + private readonly keyPath: string, lifetime: number ) { - this.publicKeyPath = `${keyPath}.pub` + this.publicKeyPath = `${this.keyPath}.pub` this.lifeTimeout = new Timeout(lifetime) this.lifeTimeout.onCompletion(async () => { @@ -28,21 +29,45 @@ export class SshKeyPair { }) } - public static async getSshKeyPair(keyPath: string, lifetime: number) { - // Overwrite key if already exists - if (await fs.existsFile(keyPath)) { - await fs.delete(keyPath) - } + private static getKeypath(keyName: string): string { + return path.join(globals.context.globalStorageUri.fsPath, keyName) + } + + public static async getSshKeyPair(keyName: string, lifetime: number) { + const keyPath = SshKeyPair.getKeypath(keyName) await SshKeyPair.generateSshKeyPair(keyPath) return new SshKeyPair(keyPath, lifetime) } - public static async generateSshKeyPair(keyPath: string): Promise { - const keyGenerated = await this.tryKeyTypes(keyPath, ['ed25519', 'rsa']) + private static isValidKeyPath(keyPath: string): boolean { + const relative = path.relative(globals.context.globalStorageUri.fsPath, keyPath) + return relative !== undefined && !relative.startsWith('..') && !path.isAbsolute(relative) && keyPath.length > 4 + } + + private static assertValidKeypath(keyPath: string, message: string): void | never { + if (!SshKeyPair.isValidKeyPath(keyPath)) { + throw new ToolkitError(message) + } + } + + private static async assertGenerated(keyPath: string, keyGenerated: boolean): Promise { if (!keyGenerated) { - throw new ToolkitError('ec2: Unable to generate ssh key pair') + throw new ToolkitError('ec2: Unable to generate ssh key pair with either ed25519 or rsa') + } + + if (!(await fs.exists(keyPath))) { + throw new ToolkitError(`ec2: Failed to generate keys, resulting key not found at ${keyPath}`) + } + } + + public static async generateSshKeyPair(keyPath: string): Promise { + const keyGenerated = await SshKeyPair.tryKeyTypes(keyPath, ['ed25519', 'rsa']) + // Should already be the case, but just in case we assert permissions. + // skip on Windows since it only allows write permission to be changed. + if (!globals.isWeb && os.platform() !== 'win32') { + await fs.chmod(keyPath, 0o600) + await SshKeyPair.assertGenerated(keyPath, keyGenerated) } - chmodSync(keyPath, 0o600) } /** * Attempts to generate an ssh key pair. Returns true if successful, false otherwise. @@ -50,7 +75,13 @@ export class SshKeyPair { * @param keyType type of key to generate. */ public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise { - return !(await tryRun('ssh-keygen', ['-t', keyType, '-N', '', '-q', '-f', keyPath], 'yes', 'unknown key type')) + const overrideKeys = async (_t: string, proc: RunParameterContext) => { + await proc.send('yes') + } + return !(await tryRun('ssh-keygen', ['-t', keyType, '-N', '', '-q', '-f', keyPath], 'yes', 'unknown key type', { + onStdout: overrideKeys, + timeout: new Timeout(5000), + })) } public static async tryKeyTypes(keyPath: string, keyTypes: sshKeyType[]): Promise { @@ -67,23 +98,27 @@ export class SshKeyPair { } public async getPublicKey(): Promise { - const contents = await fs.readFileAsString(this.publicKeyPath) + const contents = await fs.readFileText(this.publicKeyPath) return contents } public async delete(): Promise { - await fs.delete(this.keyPath) - await fs.delete(this.publicKeyPath) + SshKeyPair.assertValidKeypath( + this.keyPath, + `ec2: keyPath became invalid after creation, not deleting key at ${this.keyPath}` + ) + await fs.delete(this.keyPath, { force: true }) + await fs.delete(this.publicKeyPath, { force: true }) if (!this.lifeTimeout.completed) { this.lifeTimeout.cancel() } - - this.deleted = true } - public isDeleted(): boolean { - return this.deleted + public async isDeleted(): Promise { + const privateKeyDeleted = !(await fs.existsFile(this.getPrivateKeyPath())) + const publicKeyDeleted = !(await fs.existsFile(this.getPublicKeyPath())) + return privateKeyDeleted || publicKeyDeleted } public timeAlive(): number { diff --git a/packages/core/src/awsService/ec2/utils.ts b/packages/core/src/awsService/ec2/utils.ts index f9272f5cee1..105d2da06ff 100644 --- a/packages/core/src/awsService/ec2/utils.ts +++ b/packages/core/src/awsService/ec2/utils.ts @@ -4,6 +4,11 @@ */ import { SafeEc2Instance } from '../../shared/clients/ec2Client' +import { copyToClipboard } from '../../shared/utilities/messages' +import { Ec2Selection } from './prompter' +import { sshLogFileLocation } from '../../shared/sshConfig' +import { SSM } from 'aws-sdk' +import { getLogger } from '../../shared/logger' export function getIconCode(instance: SafeEc2Instance) { if (instance.LastSeenStatus === 'running') { @@ -14,5 +19,32 @@ export function getIconCode(instance: SafeEc2Instance) { return 'circle-slash' } + if (instance.LastSeenStatus === 'terminated') { + return 'stop' + } + return 'loading~spin' } + +export async function copyInstanceId(instanceId: string): Promise { + await copyToClipboard(instanceId, 'Id') +} + +export function getEc2SsmEnv( + selection: Ec2Selection, + ssmPath: string, + session: SSM.StartSessionResponse +): NodeJS.ProcessEnv { + return Object.assign( + { + AWS_REGION: selection.region, + AWS_SSM_CLI: ssmPath, + LOG_FILE_LOCATION: sshLogFileLocation('ec2', selection.instanceId), + STREAM_URL: session.StreamUrl, + SESSION_ID: session.SessionId, + TOKEN: session.TokenValue, + DEBUG_LOG: getLogger().logLevelEnabled('debug') ? 1 : 0, + }, + process.env + ) +} diff --git a/packages/core/src/awsService/ecs/commands.ts b/packages/core/src/awsService/ecs/commands.ts index e765919f011..acf3daa8047 100644 --- a/packages/core/src/awsService/ecs/commands.ts +++ b/packages/core/src/awsService/ecs/commands.ts @@ -11,7 +11,7 @@ import globals from '../../shared/extensionGlobals' import { ToolkitPromptSettings } from '../../shared/settings' import { ChildProcess } from '../../shared/utilities/processUtils' import { showMessageWithCancel, showOutputMessage } from '../../shared/utilities/messages' -import { formatDateTimestamp, removeAnsi } from '../../shared/utilities/textUtilities' +import { removeAnsi } from '../../shared/utilities/textUtilities' import { CancellationError, Timeout } from '../../shared/utilities/timeoutUtils' import { Commands } from '../../shared/vscode/commands2' import { EcsSettings } from './util' @@ -22,6 +22,7 @@ import { Container, Service } from './model' import { Instance } from '../../shared/utilities/typeConstructors' import { telemetry } from '../../shared/telemetry/telemetry' import { openRemoteTerminal } from '../../shared/remoteSession' +import { formatDateTimestamp } from '../../shared/datetime' async function runCommandWizard( param?: unknown, @@ -31,7 +32,7 @@ async function runCommandWizard( const wizard = new CommandWizard( container, - await ToolkitPromptSettings.instance.isPromptEnabled('ecsRunCommand'), + ToolkitPromptSettings.instance.isPromptEnabled('ecsRunCommand'), command ) const response = await wizard.run() @@ -74,7 +75,7 @@ export async function toggleExecuteCommandFlag( 'Disabling command execution will change the state of resources in your AWS account, including but not limited to stopping and restarting the service.\n Continue?' ) - if (await settings.isPromptEnabled(prompt)) { + if (settings.isPromptEnabled(prompt)) { const choice = await window.showWarningMessage(warningMessage, yes, yesDontAskAgain, no) if (choice === undefined || choice === no) { throw new CancellationError('user') diff --git a/packages/core/src/awsService/iot/commands/attachCertificate.ts b/packages/core/src/awsService/iot/commands/attachCertificate.ts index ef45917f7a0..c77f1d36d44 100644 --- a/packages/core/src/awsService/iot/commands/attachCertificate.ts +++ b/packages/core/src/awsService/iot/commands/attachCertificate.ts @@ -46,7 +46,7 @@ export async function attachCertificateCommand(node: IotThingNode, promptFun = p getLogger().debug('Attached certificate %O', cert.certificateId) - //Refresh the Thing node + // Refresh the Thing node await node.refreshNode() } diff --git a/packages/core/src/awsService/iot/commands/createCert.ts b/packages/core/src/awsService/iot/commands/createCert.ts index 9d022445bde..3f6eae413db 100644 --- a/packages/core/src/awsService/iot/commands/createCert.ts +++ b/packages/core/src/awsService/iot/commands/createCert.ts @@ -4,7 +4,6 @@ */ import * as vscode from 'vscode' -import * as fs from 'fs-extra' import * as path from 'path' import { getLogger } from '../../../shared/logger' import { localize } from '../../../shared/utilities/vsCodeUtils' @@ -12,9 +11,10 @@ import { showViewLogsMessage } from '../../../shared/utilities/messages' import { IotCertsFolderNode } from '../explorer/iotCertFolderNode' import { fileExists } from '../../../shared/filesystemUtilities' import { Iot } from 'aws-sdk' +import { fs } from '../../../shared' // eslint-disable-next-line @typescript-eslint/naming-convention -const MODE_RW_R_R = 0o644 //File permission 0644 rw-r--r-- for PEM files. +const MODE_RW_R_R = 0o644 // File permission 0644 rw-r--r-- for PEM files. // eslint-disable-next-line @typescript-eslint/naming-convention const PEM_FILE_ENCODING = 'ascii' @@ -60,10 +60,10 @@ export async function createCertificateCommand( getLogger().info(`Downloaded certificate ${certId}`) void vscode.window.showInformationMessage(localize('AWS.iot.createCert.success', 'Created certificate {0}', certId)) - //Save resources + // Save resources const saveSuccessful = await saveFunc(folderLocation, certId!, certPem, privateKey, publicKey) if (!saveSuccessful) { - //Delete the certificate if the key pair cannot be saved + // Delete the certificate if the key pair cannot be saved try { await node.iot.deleteCertificate({ certificateId: certId! }) } catch (e) { @@ -72,7 +72,7 @@ export async function createCertificateCommand( } } - //Refresh the Certificate Folder node + // Refresh the Certificate Folder node await node.refreshNode() } @@ -115,14 +115,14 @@ async function saveCredentials( const publicKeyExists = await fileExists(publicKeyPath) if (certExists) { - getLogger().error('Certificate path {0} already exists', certPath) + getLogger().error('Certificate path %s already exists', certPath) void vscode.window.showErrorMessage( localize('AWS.iot.createCert.error', 'Failed to create certificate. Path {0} already exists.', certPath) ) return false } if (privateKeyExists) { - getLogger().error('Key path {0} already exists', privateKeyPath) + getLogger().error('Key path %s already exists', privateKeyPath) void vscode.window.showErrorMessage( localize( 'AWS.iot.createCert.error', @@ -133,7 +133,7 @@ async function saveCredentials( return false } if (publicKeyExists) { - getLogger().error('Key path {0} already exists', publicKeyPath) + getLogger().error('Key path %s already exists', publicKeyPath) void vscode.window.showErrorMessage( localize( 'AWS.iot.createCert.error', diff --git a/packages/core/src/awsService/iot/commands/createPolicy.ts b/packages/core/src/awsService/iot/commands/createPolicy.ts index cf020506e39..ba082811e34 100644 --- a/packages/core/src/awsService/iot/commands/createPolicy.ts +++ b/packages/core/src/awsService/iot/commands/createPolicy.ts @@ -4,11 +4,11 @@ */ import * as vscode from 'vscode' -import * as fs from 'fs-extra' import { getLogger } from '../../../shared/logger' import { localize } from '../../../shared/utilities/vsCodeUtils' import { showViewLogsMessage } from '../../../shared/utilities/messages' import { IotPolicyFolderNode } from '../explorer/iotPolicyFolderNode' +import { fs } from '../../../shared' /** * Creates a policy from a policy document. @@ -33,7 +33,7 @@ export async function createPolicyCommand(node: IotPolicyFolderNode, getPolicyDo } try { - //Parse to ensure this is a valid JSON + // Parse to ensure this is a valid JSON const policyJSON = JSON.parse(data.toString()) await node.iot.createPolicy({ policyName, policyDocument: JSON.stringify(policyJSON) }) void vscode.window.showInformationMessage( @@ -45,11 +45,11 @@ export async function createPolicyCommand(node: IotPolicyFolderNode, getPolicyDo return } - //Refresh the Policy Folder node + // Refresh the Policy Folder node await node.refreshNode() } -export async function getPolicyDocument(): Promise { +export async function getPolicyDocument(): Promise { const fileLocation = await vscode.window.showOpenDialog({ canSelectFolders: false, canSelectFiles: true, @@ -64,9 +64,9 @@ export async function getPolicyDocument(): Promise { const policyLocation = fileLocation[0] - let data: Buffer + let data: Uint8Array try { - data = await fs.readFile(policyLocation.fsPath) + data = await fs.readFileBytes(policyLocation.fsPath) } catch (e) { getLogger().error('Failed to read policy document: %s', e) void showViewLogsMessage(localize('AWS.iot.createPolicy.error', 'Failed to read policy document')) diff --git a/packages/core/src/awsService/iot/commands/createPolicyVersion.ts b/packages/core/src/awsService/iot/commands/createPolicyVersion.ts index 979c8d50beb..257626489f6 100644 --- a/packages/core/src/awsService/iot/commands/createPolicyVersion.ts +++ b/packages/core/src/awsService/iot/commands/createPolicyVersion.ts @@ -27,7 +27,7 @@ export async function createPolicyVersionCommand( } try { - //Parse to ensure this is a valid JSON + // Parse to ensure this is a valid JSON const policyJSON = JSON.parse(data.toString()) await node.iot.createPolicyVersion({ policyName, @@ -45,6 +45,6 @@ export async function createPolicyVersionCommand( return } - //Refresh the node + // Refresh the node node.refresh() } diff --git a/packages/core/src/awsService/iot/commands/createThing.ts b/packages/core/src/awsService/iot/commands/createThing.ts index 6981e48f192..2d9b1f11d4e 100644 --- a/packages/core/src/awsService/iot/commands/createThing.ts +++ b/packages/core/src/awsService/iot/commands/createThing.ts @@ -43,7 +43,7 @@ export async function createThingCommand(node: IotThingFolderNode): Promise { void showViewLogsMessage(localize('AWS.iot.deleteThing.error', 'Failed to delete Thing: {0}', thingName)) } - //Refresh the Things Folder node + // Refresh the Things Folder node await node.parent.refreshNode() } diff --git a/packages/core/src/awsService/iot/commands/detachCert.ts b/packages/core/src/awsService/iot/commands/detachCert.ts index 11489fa841d..a8384e34b79 100644 --- a/packages/core/src/awsService/iot/commands/detachCert.ts +++ b/packages/core/src/awsService/iot/commands/detachCert.ts @@ -49,6 +49,6 @@ export async function detachThingCertCommand(node: IotThingCertNode): Promise = Settings.instance ) { - //Show only 8 characters in the explorer instead of the full 64. The entire - //ID can be copied from the context menu or viewed when hovered over. + // Show only 8 characters in the explorer instead of the full 64. The entire + // ID can be copied from the context menu or viewed when hovered over. super(truncate(certificate.id, 8), collapsibleState) this.tooltip = localize( @@ -50,7 +51,7 @@ export abstract class IotCertificateNode extends AWSTreeNodeBase implements AWSR this.certificate.id, this.certificate.activeStatus, formatLocalized(this.certificate.creationDate), - things?.length ?? 0 > 0 ? `\nAttached to: ${things!.join(', ')}` : '' + (things?.length ?? 0 > 0) ? `\nAttached to: ${things!.join(', ')}` : '' ) this.iconPath = getIcon('aws-iot-certificate') this.description = `\t[${this.certificate.activeStatus}]` diff --git a/packages/core/src/awsService/iot/explorer/iotPolicyFolderNode.ts b/packages/core/src/awsService/iot/explorer/iotPolicyFolderNode.ts index 8d9e1ce809e..36af63cf495 100644 --- a/packages/core/src/awsService/iot/explorer/iotPolicyFolderNode.ts +++ b/packages/core/src/awsService/iot/explorer/iotPolicyFolderNode.ts @@ -19,10 +19,10 @@ import { IotNode } from './iotNodes' import { Settings } from '../../../shared/settings' import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils' -//Length of certificate ID. The certificate ID is the last segment of the ARN. +// Length of certificate ID. The certificate ID is the last segment of the ARN. const certIdLength = 64 -//Number of digits of the certificate ID to show +// Number of digits of the certificate ID to show const certPreviewLength = 8 /** diff --git a/packages/core/src/awsService/iot/explorer/iotPolicyNode.ts b/packages/core/src/awsService/iot/explorer/iotPolicyNode.ts index 790a5864e6f..3f9b7003d60 100644 --- a/packages/core/src/awsService/iot/explorer/iotPolicyNode.ts +++ b/packages/core/src/awsService/iot/explorer/iotPolicyNode.ts @@ -39,7 +39,7 @@ export class IotPolicyNode extends AWSTreeNodeBase implements AWSResourceNode { 'AWS.explorerNode.iot.policyToolTip', '{0}{1}', policy.name, - certs?.length ?? 0 > 0 ? `\nAttached to: ${certs?.join(', ')}` : '' + (certs?.length ?? 0 > 0) ? `\nAttached to: ${certs?.join(', ')}` : '' ) this.iconPath = getIcon('aws-iot-policy') this.contextValue = 'awsIotPolicyNode.Certificates' diff --git a/packages/core/src/awsService/iot/explorer/iotPolicyVersionNode.ts b/packages/core/src/awsService/iot/explorer/iotPolicyVersionNode.ts index 6d2451fbf66..ab2dd0f872d 100644 --- a/packages/core/src/awsService/iot/explorer/iotPolicyVersionNode.ts +++ b/packages/core/src/awsService/iot/explorer/iotPolicyVersionNode.ts @@ -10,7 +10,7 @@ import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' import { inspect } from 'util' import { IotPolicyWithVersionsNode } from './iotPolicyNode' import { localize } from '../../../shared/utilities/vsCodeUtils' -import { formatLocalized } from '../../../shared/utilities/textUtilities' +import { formatLocalized } from '../../../shared/datetime' /** * Represents an IoT Policy that may have either a Certificate Node or the diff --git a/packages/core/src/awsService/redshift/activation.ts b/packages/core/src/awsService/redshift/activation.ts index 58dea2e0075..e7af970c26a 100644 --- a/packages/core/src/awsService/redshift/activation.ts +++ b/packages/core/src/awsService/redshift/activation.ts @@ -99,7 +99,7 @@ function getNotebookConnectClickedHandler(ctx: ExtContext, redshiftNotebookContr connectionParams = undefined } const edit = new vscode.WorkspaceEdit() - //NotebookEdit is only available for engine version > 1.68.0 + // NotebookEdit is only available for engine version > 1.68.0 const nbEdit = (vscode as any).NotebookEdit.updateNotebookMetadata({ connectionParams: connectionParams, }) diff --git a/packages/core/src/awsService/redshift/explorer/redshiftNode.ts b/packages/core/src/awsService/redshift/explorer/redshiftNode.ts index 4d923192f22..4c245b7f4de 100644 --- a/packages/core/src/awsService/redshift/explorer/redshiftNode.ts +++ b/packages/core/src/awsService/redshift/explorer/redshiftNode.ts @@ -127,7 +127,7 @@ export class RedshiftNode extends AWSTreeNodeBase implements LoadMoreNode { newServerlessToken = response.nextToken ?? '' } } catch (error) { - getLogger().error("Serverless workgroup operation isn't supported or failed:", error) + getLogger().error("Serverless workgroup operation isn't supported or failed: %O", error) // Continue without interrupting the provisioned cluster loading } } @@ -143,7 +143,7 @@ export class RedshiftNode extends AWSTreeNodeBase implements LoadMoreNode { } public async createCluster(clusterName: string): Promise { - //Code for creating redshiftClient cluster + // Code for creating redshiftClient cluster } public [inspect.custom](): string { diff --git a/packages/core/src/awsService/redshift/notebook/redshiftNotebookController.ts b/packages/core/src/awsService/redshift/notebook/redshiftNotebookController.ts index 3a0c3ca2d9d..1db972314d3 100644 --- a/packages/core/src/awsService/redshift/notebook/redshiftNotebookController.ts +++ b/packages/core/src/awsService/redshift/notebook/redshiftNotebookController.ts @@ -122,13 +122,13 @@ export class RedshiftNotebookController { } let tableHtml = `

Results from ${connectionParams.warehouseIdentifier} - database: ${connectionParams.database}

` - //Adding column headers + // Adding column headers for (const column of columns) { tableHtml += `` } tableHtml += '' - //Adding data rows + // Adding data rows for (const row of records) { tableHtml += '' for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { diff --git a/packages/core/src/awsService/s3/activation.ts b/packages/core/src/awsService/s3/activation.ts index fcaee4f3905..2b8a164800a 100644 --- a/packages/core/src/awsService/s3/activation.ts +++ b/packages/core/src/awsService/s3/activation.ts @@ -25,6 +25,8 @@ import { Commands } from '../../shared/vscode/commands2' import * as nls from 'vscode-nls' import { DefaultS3Client } from '../../shared/clients/s3Client' +import { TreeNode } from '../../shared/treeview/resourceTreeDataProvider' +import { getSourceNode } from '../../shared/utilities/treeNodeUtils' const localize = nls.loadMessageBundle() /** @@ -58,7 +60,7 @@ export async function activate(ctx: ExtContext): Promise { }), Commands.register( { id: 'aws.s3.uploadFile', autoconnect: true }, - async (node?: S3BucketNode | S3FolderNode) => { + async (node?: S3BucketNode | S3FolderNode | TreeNode) => { if (!node) { const awsContext = ctx.awsContext const regionCode = awsContext.getCredentialDefaultRegion() @@ -66,7 +68,8 @@ export async function activate(ctx: ExtContext): Promise { const document = vscode.window.activeTextEditor?.document.uri await uploadFileCommand(s3Client, document) } else { - await uploadFileCommand(node.s3, node) + const sourceNode = getSourceNode(node) + await uploadFileCommand(sourceNode.s3, sourceNode) } } ), @@ -76,11 +79,13 @@ export async function activate(ctx: ExtContext): Promise { Commands.register('aws.s3.createBucket', async (node: S3Node) => { await createBucketCommand(node) }), - Commands.register('aws.s3.createFolder', async (node: S3BucketNode | S3FolderNode) => { - await createFolderCommand(node) + Commands.register('aws.s3.createFolder', async (node: S3BucketNode | S3FolderNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await createFolderCommand(sourceNode) }), - Commands.register('aws.s3.deleteBucket', async (node: S3BucketNode) => { - await deleteBucketCommand(node) + Commands.register('aws.s3.deleteBucket', async (node: S3BucketNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await deleteBucketCommand(sourceNode) }), Commands.register('aws.s3.deleteFile', async (node: S3FileNode) => { await deleteFileCommand(node) diff --git a/packages/core/src/awsService/s3/commands/uploadFile.ts b/packages/core/src/awsService/s3/commands/uploadFile.ts index 43b17aa81c4..41093a96f2f 100644 --- a/packages/core/src/awsService/s3/commands/uploadFile.ts +++ b/packages/core/src/awsService/s3/commands/uploadFile.ts @@ -6,7 +6,7 @@ import * as path from 'path' import * as mime from 'mime-types' import * as vscode from 'vscode' -import { statSync } from 'fs' +import { statSync } from 'fs' // eslint-disable-line no-restricted-imports import { S3 } from 'aws-sdk' import { getLogger } from '../../../shared/logger' import { S3Node } from '../explorer/s3Nodes' @@ -111,8 +111,8 @@ export async function uploadFileCommand( const filesToUpload = await getFile(document) if (!filesToUpload || filesToUpload.length === 0) { - //if file is undefined, means the back button was pressed(there is no step before) or no file was selected - //thus break the loop of the 'wizard' + // if file is undefined, means the back button was pressed(there is no step before) or no file was selected + // thus break the loop of the 'wizard' showOutputMessage( localize( 'AWS.message.error.uploadFileCommand.noFileSelected', @@ -232,7 +232,7 @@ async function runBatchUploads(uploadRequests: UploadRequest[], outputChannel = outputChannel ) } - //at least one request failed + // at least one request failed const response = await vscode.window.showErrorMessage( localize( 'AWS.s3.uploadFile.retryPrompt', @@ -325,7 +325,7 @@ async function uploadBatchOfFiles( }) if (uploadResult) { - //this request failed to upload + // this request failed to upload failedRequests.push(uploadResult) } @@ -415,7 +415,7 @@ export async function promptUserForBucket( try { allBuckets = await s3client.listAllBuckets() } catch (e) { - getLogger().error('Failed to list buckets from client', e) + getLogger().error('Failed to list buckets from client %O', e) void vscode.window.showErrorMessage( localize('AWS.message.error.promptUserForBucket.listBuckets', 'Failed to list buckets from client') ) diff --git a/packages/core/src/awsService/s3/explorer/s3FileNode.ts b/packages/core/src/awsService/s3/explorer/s3FileNode.ts index 7a4cd3a5d37..31b6429d265 100644 --- a/packages/core/src/awsService/s3/explorer/s3FileNode.ts +++ b/packages/core/src/awsService/s3/explorer/s3FileNode.ts @@ -12,9 +12,9 @@ import { inspect } from 'util' import { S3BucketNode } from './s3BucketNode' import { S3FolderNode } from './s3FolderNode' import globals from '../../../shared/extensionGlobals' -import { formatLocalized, getRelativeDate } from '../../../shared/utilities/textUtilities' import { isCloud9 } from '../../../shared/extensionUtilities' import { getIcon } from '../../../shared/icons' +import { formatLocalized, getRelativeDate } from '../../../shared/datetime' /** * Represents an object in an S3 bucket. diff --git a/packages/core/src/awsService/s3/fileViewerManager.ts b/packages/core/src/awsService/s3/fileViewerManager.ts index 336737b027e..d800a3bfeee 100644 --- a/packages/core/src/awsService/s3/fileViewerManager.ts +++ b/packages/core/src/awsService/s3/fileViewerManager.ts @@ -346,7 +346,7 @@ export class S3FileViewerManager { } private async showEditNotification(): Promise { - if (!(await this.settings.isPromptEnabled(promptOnEditKey))) { + if (!this.settings.isPromptEnabled(promptOnEditKey)) { return } diff --git a/packages/core/src/awsexplorer/activation.ts b/packages/core/src/awsexplorer/activation.ts index a93baef6034..5ea7295bf98 100644 --- a/packages/core/src/awsexplorer/activation.ts +++ b/packages/core/src/awsexplorer/activation.ts @@ -32,6 +32,10 @@ import { activateViewsShared, registerToolView } from './activationShared' import { isExtensionInstalled } from '../shared/utilities' import { CommonAuthViewProvider } from '../login/webview' import { setContext } from '../shared' +import { TreeNode } from '../shared/treeview/resourceTreeDataProvider' +import { getSourceNode } from '../shared/utilities/treeNodeUtils' +import { openAwsCFNConsoleCommand, openAwsConsoleCommand } from '../shared/awsConsole' +import { StackNameNode } from '../awsService/appBuilder/explorer/nodes/deployedStack' /** * Activates the AWS Explorer UI and related functionality. @@ -121,6 +125,7 @@ export async function activate(args: { refreshCommands: [refreshAmazonQ, refreshAmazonQRootNode], }) } + const viewNodes: ToolView[] = [ ...amazonQViewNode, ...codecatalystViewNode, @@ -196,8 +201,21 @@ async function registerAwsExplorerCommands( isPreviewAndRender: true, }) ), - Commands.register('aws.copyArn', async (node: AWSResourceNode) => await copyTextCommand(node, 'ARN')), - Commands.register('aws.copyName', async (node: AWSResourceNode) => await copyTextCommand(node, 'name')), + Commands.register('aws.copyArn', async (node: AWSResourceNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await copyTextCommand(sourceNode, 'ARN') + }), + Commands.register('aws.copyName', async (node: AWSResourceNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await copyTextCommand(sourceNode, 'name') + }), + Commands.register('aws.openAwsConsole', async (node: AWSResourceNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await openAwsConsoleCommand(sourceNode) + }), + Commands.register('aws.openAwsCFNConsole', async (node: StackNameNode) => { + await openAwsCFNConsoleCommand(node) + }), Commands.register('aws.refreshAwsExplorerNode', async (element: AWSTreeNodeBase | undefined) => { awsExplorer.refresh(element) }), diff --git a/packages/core/src/awsexplorer/activationShared.ts b/packages/core/src/awsexplorer/activationShared.ts index 6a474ccf2e2..9f488a35c36 100644 --- a/packages/core/src/awsexplorer/activationShared.ts +++ b/packages/core/src/awsexplorer/activationShared.ts @@ -27,4 +27,6 @@ export function registerToolView(viewNode: ToolView, context: vscode.ExtensionCo telemetry.cdk_appExpanded.emit() } }) + + return toolView } diff --git a/packages/core/src/awsexplorer/regionNode.ts b/packages/core/src/awsexplorer/regionNode.ts index 98fb14c369c..5e6e0b06d52 100644 --- a/packages/core/src/awsexplorer/regionNode.ts +++ b/packages/core/src/awsexplorer/regionNode.ts @@ -29,9 +29,9 @@ import { DefaultSchemaClient } from '../shared/clients/schemaClient' import { getEcsRootNode } from '../awsService/ecs/model' import { compareTreeItems, TreeShim } from '../shared/treeview/utils' import { Ec2ParentNode } from '../awsService/ec2/explorer/ec2ParentNode' -import { DevSettings } from '../shared/settings' import { Ec2Client } from '../shared/clients/ec2Client' import { isCloud9 } from '../shared/extensionUtilities' +import { Experiments } from '../shared/settings' interface ServiceNode { allRegions?: boolean @@ -65,7 +65,7 @@ const serviceCandidates: ServiceNode[] = [ }, { serviceId: 'ec2', - when: () => DevSettings.instance.isDevMode(), + when: () => Experiments.instance.isExperimentEnabled('ec2RemoteConnect'), createFn: (regionCode: string, partitionId: string) => new Ec2ParentNode(regionCode, partitionId, new Ec2Client(regionCode)), }, diff --git a/packages/core/src/codecatalyst/activation.ts b/packages/core/src/codecatalyst/activation.ts index 812766f5687..4a2385559f4 100644 --- a/packages/core/src/codecatalyst/activation.ts +++ b/packages/core/src/codecatalyst/activation.ts @@ -128,7 +128,7 @@ export async function activate(ctx: ExtContext): Promise { await showReadmeFileOnFirstLoad(ctx.extensionContext.workspaceState) const settings = ToolkitPromptSettings.instance - if (await settings.isPromptEnabled('remoteConnected')) { + if (settings.isPromptEnabled('remoteConnected')) { const message = localize( 'AWS.codecatalyst.connectedMessage', 'Welcome to your Amazon CodeCatalyst Dev Environment. For more options and information, view Dev Environment settings ({0} Extension > CodeCatalyst).', diff --git a/packages/core/src/codecatalyst/auth.ts b/packages/core/src/codecatalyst/auth.ts index 778ad6abe92..11e31a37c4f 100644 --- a/packages/core/src/codecatalyst/auth.ts +++ b/packages/core/src/codecatalyst/auth.ts @@ -26,11 +26,11 @@ import { hasExactScopes, } from '../auth/connection' import { createBuilderIdConnection } from '../auth/utils' -import { builderIdStartUrl } from '../auth/sso/model' import { showReauthenticateMessage } from '../shared/utilities/messages' import { ToolkitPromptSettings } from '../shared/settings' import { setContext } from '../shared/vscode/setContext' import { withTelemetryContext } from '../shared/telemetry/util' +import { builderIdStartUrl } from '../auth/sso/constants' // Secrets stored on the macOS keychain appear as individual entries for each key // This is fine so long as the user has only a few accounts. Otherwise this should diff --git a/packages/core/src/codecatalyst/devEnv.ts b/packages/core/src/codecatalyst/devEnv.ts index cd45b353358..44db151d8de 100644 --- a/packages/core/src/codecatalyst/devEnv.ts +++ b/packages/core/src/codecatalyst/devEnv.ts @@ -98,7 +98,7 @@ export class DevEnvActivityStarter { } // If user is not authenticated, assume 15 minutes. const inactivityTimeoutMin = - devenvTimeoutMs > 0 ? devenvTimeoutMs / 60000 : thisDevenv?.summary.inactivityTimeoutMinutes ?? 15 + devenvTimeoutMs > 0 ? devenvTimeoutMs / 60000 : (thisDevenv?.summary.inactivityTimeoutMinutes ?? 15) if (!shouldSendActivity(inactivityTimeoutMin)) { getLogger().info( `codecatalyst: disabling DevEnvActivity heartbeat: configured to never timeout (inactivityTimeoutMinutes=${inactivityTimeoutMin})` diff --git a/packages/core/src/codecatalyst/model.ts b/packages/core/src/codecatalyst/model.ts index e203693523d..768a97890ee 100644 --- a/packages/core/src/codecatalyst/model.ts +++ b/packages/core/src/codecatalyst/model.ts @@ -18,9 +18,7 @@ import { DevEnvClient } from '../shared/clients/devenvClient' import { getLogger } from '../shared/logger' import { AsyncCollection, toCollection } from '../shared/utilities/asyncCollection' import { getCodeCatalystSpaceName, getCodeCatalystProjectName, getCodeCatalystDevEnvId } from '../shared/vscode/env' -import { writeFile } from 'fs-extra' import { sshAgentSocketVariable, startSshAgent, startVscodeRemote } from '../shared/extensions/ssh' -import { ChildProcess } from '../shared/utilities/processUtils' import { isDevenvVscode } from './utils' import { Timeout } from '../shared/utilities/timeoutUtils' import { Commands } from '../shared/vscode/commands2' @@ -29,8 +27,9 @@ import { fileExists } from '../shared/filesystemUtilities' import { CodeCatalystAuthenticationProvider } from './auth' import { ToolkitError } from '../shared/errors' import { Result } from '../shared/utilities/result' -import { VscodeRemoteConnection, ensureDependencies } from '../shared/remoteSession' +import { EnvProvider, VscodeRemoteConnection, createBoundProcess, ensureDependencies } from '../shared/remoteSession' import { SshConfig, sshLogFileLocation } from '../shared/sshConfig' +import { fs } from '../shared' export type DevEnvironmentId = Pick export const connectScriptPrefix = 'codecatalyst_connect' @@ -111,30 +110,8 @@ export function createCodeCatalystEnvProvider( } } -type EnvProvider = () => Promise - -/** - * Creates a new {@link ChildProcess} class bound to a specific dev environment. All instances of this - * derived class will have SSM session information injected as environment variables as-needed. - */ -export function createBoundProcess(envProvider: EnvProvider): typeof ChildProcess { - type Run = ChildProcess['run'] - return class SessionBoundProcess extends ChildProcess { - public override async run(...args: Parameters): ReturnType { - const options = args[0] - const envVars = await envProvider() - const spawnOptions = { - ...options?.spawnOptions, - env: { ...envVars, ...options?.spawnOptions?.env }, - } - - return super.run({ ...options, spawnOptions }) - } - } -} - export async function cacheBearerToken(bearerToken: string, devenvId: string): Promise { - await writeFile(bearerTokenCacheLocation(devenvId), `${bearerToken}`, 'utf8') + await fs.writeFile(bearerTokenCacheLocation(devenvId), `${bearerToken}`, 'utf8') } export function bearerTokenCacheLocation(devenvId: string): string { diff --git a/packages/core/src/codecatalyst/uriHandlers.ts b/packages/core/src/codecatalyst/uriHandlers.ts index 751b7d20f79..b34dd25761b 100644 --- a/packages/core/src/codecatalyst/uriHandlers.ts +++ b/packages/core/src/codecatalyst/uriHandlers.ts @@ -7,9 +7,9 @@ import * as vscode from 'vscode' import { SearchParams, UriHandler } from '../shared/vscode/uriHandler' import { getCodeCatalystConfig } from '../shared/clients/codecatalystClient' import { CodeCatalystCommands } from './commands' -import { builderIdStartUrl } from '../auth/sso/model' import { defaultSsoRegion } from '../auth/connection' import { getLogger } from '../shared/logger' +import { builderIdStartUrl } from '../auth/sso/constants' type ConnectParams = { devEnvironmentId: string diff --git a/packages/core/src/codecatalyst/wizards/selectResource.ts b/packages/core/src/codecatalyst/wizards/selectResource.ts index 7b0a100825d..d1c3de3328b 100644 --- a/packages/core/src/codecatalyst/wizards/selectResource.ts +++ b/packages/core/src/codecatalyst/wizards/selectResource.ts @@ -14,11 +14,11 @@ import { QuickPickPrompter, } from '../../shared/ui/pickerPrompter' import { AsyncCollection } from '../../shared/utilities/asyncCollection' -import { getRelativeDate } from '../../shared/utilities/textUtilities' import { isValidResponse } from '../../shared/wizards/wizard' import { associateDevEnv, docs } from '../model' import { getHelpUrl, isDevenvVscode } from '../utils' import { getLogger } from '../../shared/logger/logger' +import { getRelativeDate } from '../../shared/datetime' export function createRepoLabel(r: codecatalyst.CodeCatalystRepo): string { return `${r.org.name} / ${r.project.name} / ${r.name}` diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index ff78d8a99ef..4034c6397f4 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -10,7 +10,15 @@ import { KeyStrokeHandler } from './service/keyStrokeHandler' import * as EditorContext from './util/editorContext' import * as CodeWhispererConstants from './models/constants' import { getCompletionItems } from './service/completionProvider' -import { vsCodeState, ConfigurationEntry, CodeSuggestionsState, CodeScansState } from './models/model' +import { + vsCodeState, + ConfigurationEntry, + CodeSuggestionsState, + CodeScansState, + SecurityTreeViewFilterState, + AggregatedCodeScanIssue, + CodeScanIssue, +} from './models/model' import { invokeRecommendation } from './commands/invokeRecommendation' import { acceptSuggestion } from './commands/onInlineAcceptance' import { resetIntelliSenseState } from './util/globalStateUtil' @@ -41,12 +49,27 @@ import { signoutCodeWhisperer, toggleCodeScans, registerToolkitApiCallback, + showFileScan, + clearFilters, + generateFix, + explainIssue, + ignoreIssue, + rejectFix, + showSecurityIssueFilters, + regenerateFix, + ignoreAllIssues, + focusIssue, + showExploreAgentsView, } from './commands/basicCommands' import { sleep } from '../shared/utilities/timeoutUtils' import { ReferenceLogViewProvider } from './service/referenceLogViewProvider' import { ReferenceHoverProvider } from './service/referenceHoverProvider' import { ReferenceInlineProvider } from './service/referenceInlineProvider' -import { disposeSecurityDiagnostic, securityScanRender } from './service/diagnosticsProvider' +import { + disposeSecurityDiagnostic, + securityScanRender, + updateSecurityDiagnosticCollection, +} from './service/diagnosticsProvider' import { SecurityPanelViewProvider, openEditorAtRange } from './views/securityPanelViewProvider' import { RecommendationHandler } from './service/recommendationHandler' import { Commands, registerCommandErrorHandler, registerDeclaredCommands } from '../shared/vscode/commands2' @@ -71,6 +94,11 @@ import { logAndShowError, logAndShowWebviewError } from '../shared/utilities/log import { openSettings } from '../shared/settings' import { telemetry } from '../shared/telemetry' import { FeatureConfigProvider } from '../shared/featureConfig' +import { SecurityIssueProvider } from './service/securityIssueProvider' +import { SecurityIssueTreeViewProvider } from './service/securityIssueTreeViewProvider' +import { setContext } from '../shared/vscode/setContext' +import { syncSecurityIssueWebview } from './views/securityIssue/securityIssueWebview' +import { detectCommentAboveLine } from '../shared/utilities/commentUtils' let localize: nls.LocalizeFunc @@ -114,7 +142,7 @@ export async function activate(context: ExtContext): Promise { // TODO: this is already done in packages/core/src/extensionCommon.ts, why doesn't amazonq use that? registerWebviewErrorHandler((error: unknown, webviewId: string, command: string) => { - logAndShowWebviewError(localize, error, webviewId, command) + return logAndShowWebviewError(localize, error, webviewId, command) }) /** @@ -186,6 +214,11 @@ export async function activate(context: ExtContext): Promise { if (configurationChangeEvent.affectsConfiguration('http.proxy')) { updateUserProxyUrl() } + + if (configurationChangeEvent.affectsConfiguration('amazonQ.ignoredSecurityIssues')) { + const ignoredIssues = CodeWhispererSettings.instance.getIgnoredSecurityIssues() + toggleIssuesVisibility((issue) => !ignoredIssues.includes(issue.title)) + } }), /** * Open Configuration @@ -222,8 +255,10 @@ export async function activate(context: ExtContext): Promise { toggleCodeScans.register(CodeScansState.instance), // enable code suggestions enableCodeSuggestions.register(context), - // code scan + // project scan showSecurityScan.register(context, securityPanelViewProvider, client), + // on demand file scan + showFileScan.register(context, securityPanelViewProvider, client), // show security issue webview panel openSecurityIssuePanel.register(context), // sign in with sso or AWS ID @@ -238,10 +273,40 @@ export async function activate(context: ExtContext): Promise { updateReferenceLog.register(), // refresh codewhisperer status bar refreshStatusBar.register(), + // generate code fix + generateFix.register(client, context), + // regenerate code fix + regenerateFix.register(), // apply suggested fix applySecurityFix.register(), + // reject suggested fix + rejectFix.register(context.extensionContext), + // ignore issues by title + ignoreAllIssues.register(), + // ignore single issue + ignoreIssue.register(), + // explain issue + explainIssue.register(), // quick pick with codewhisperer options listCodeWhispererCommands.register(), + // quick pick with security issues tree filters + showSecurityIssueFilters.register(), + // reset security issue filters + clearFilters.register(), + // handle security issues tree item clicked + focusIssue.register(), + // refresh the treeview on every change + SecurityTreeViewFilterState.instance.onDidChangeState((e) => { + SecurityIssueTreeViewProvider.instance.refresh() + }), + // show a no match state + SecurityIssueTreeViewProvider.instance.onDidChangeTreeData((e) => { + const noMatches = + Array.isArray(e) && + e.length === 0 && + SecurityIssueProvider.instance.issues.some((group) => group.issues.some((issue) => issue.visible)) + void setContext('aws.amazonq.security.noMatches', noMatches) + }), // manual trigger Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => { invokeRecommendation( @@ -278,6 +343,7 @@ export async function activate(context: ExtContext): Promise { ), vscode.window.registerWebviewViewProvider(ReferenceLogViewProvider.viewType, ReferenceLogViewProvider.instance), showReferenceLog.register(), + showExploreAgentsView.register(), vscode.languages.registerCodeLensProvider( [...CodeWhispererConstants.platformLanguageIds], ReferenceInlineProvider.instance @@ -328,6 +394,8 @@ export async function activate(context: ExtContext): Promise { */ setSubscriptionsForAutoScans() + setSubscriptionsForCodeIssues() + function shouldRunAutoScan(editor: vscode.TextEditor | undefined, isScansEnabled?: boolean) { return ( (isScansEnabled ?? CodeScansState.instance.isScansEnabled()) && @@ -349,7 +417,8 @@ export async function activate(context: ExtContext): Promise { editor, client, context.extensionContext, - CodeWhispererConstants.CodeAnalysisScope.FILE + CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO, + false ) } @@ -373,7 +442,8 @@ export async function activate(context: ExtContext): Promise { editor, client, context.extensionContext, - CodeWhispererConstants.CodeAnalysisScope.FILE + CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO, + false ) } }), @@ -391,7 +461,8 @@ export async function activate(context: ExtContext): Promise { editor, client, context.extensionContext, - CodeWhispererConstants.CodeAnalysisScope.FILE + CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO, + false ) } }) @@ -406,7 +477,8 @@ export async function activate(context: ExtContext): Promise { editor, client, context.extensionContext, - CodeWhispererConstants.CodeAnalysisScope.FILE + CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO, + false ) } }) @@ -478,18 +550,10 @@ export async function activate(context: ExtContext): Promise { if (e.document !== editor.document) { return } - if (!runtimeLanguageContext.isLanguageSupported(e.document.languageId)) { + if (!runtimeLanguageContext.isLanguageSupported(e.document)) { return } - /** - * CodeWhisperer security panel dynamic handling - */ - disposeSecurityDiagnostic(e) - - SecurityIssueHoverProvider.instance.handleDocumentChange(e) - SecurityIssueCodeActionProvider.instance.handleDocumentChange(e) - CodeWhispererCodeCoverageTracker.getTracker(e.document.languageId)?.countTotalTokens(e) /** @@ -549,7 +613,7 @@ export async function activate(context: ExtContext): Promise { if (e.document !== editor.document) { return } - if (!runtimeLanguageContext.isLanguageSupported(e.document.languageId)) { + if (!runtimeLanguageContext.isLanguageSupported(e.document)) { return } /** @@ -602,6 +666,35 @@ export async function activate(context: ExtContext): Promise { await Commands.tryExecute('aws.amazonq.refreshConnectionCallback') container.ready() + + function setSubscriptionsForCodeIssues() { + context.extensionContext.subscriptions.push( + vscode.workspace.onDidChangeTextDocument(async (e) => { + if (e.document.uri.scheme !== 'file') { + return + } + const diagnostics = securityScanRender.securityDiagnosticCollection?.get(e.document.uri) + if (!diagnostics || diagnostics.length === 0) { + return + } + disposeSecurityDiagnostic(e) + + SecurityIssueProvider.instance.handleDocumentChange(e) + SecurityIssueTreeViewProvider.instance.refresh() + await syncSecurityIssueWebview(context) + + toggleIssuesVisibility((issue, filePath) => + filePath !== e.document.uri.fsPath + ? issue.visible + : !detectCommentAboveLine( + e.document, + issue.startLine, + CodeWhispererConstants.amazonqIgnoreNextLine + ) + ) + }) + ) + } } export async function shutdown() { @@ -618,6 +711,17 @@ export async function enableDefaultConfigCloud9() { await editorSettings.update('acceptSuggestionOnEnter', 'on', vscode.ConfigurationTarget.Global) await editorSettings.update('snippetSuggestions', 'top', vscode.ConfigurationTarget.Global) } catch (error) { - getLogger().error('amazonq: Failed to update user settings', error) + getLogger().error('amazonq: Failed to update user settings %O', error) } } + +function toggleIssuesVisibility(visibleCondition: (issue: CodeScanIssue, filePath: string) => boolean) { + const updatedIssues: AggregatedCodeScanIssue[] = SecurityIssueProvider.instance.issues.map((group) => ({ + ...group, + issues: group.issues.map((issue) => ({ ...issue, visible: visibleCondition(issue, group.filePath) })), + })) + securityScanRender.securityDiagnosticCollection?.clear() + updatedIssues.forEach((issue) => updateSecurityDiagnosticCollection(issue)) + SecurityIssueProvider.instance.issues = updatedIssues + SecurityIssueTreeViewProvider.instance.refresh() +} diff --git a/packages/core/src/codewhisperer/client/agent.ts b/packages/core/src/codewhisperer/client/agent.ts index d825e94d8d1..52152d2a616 100644 --- a/packages/core/src/codewhisperer/client/agent.ts +++ b/packages/core/src/codewhisperer/client/agent.ts @@ -82,6 +82,6 @@ export function initializeNetworkAgent(): void { } } catch (error) { // Log any errors in the patching logic - getLogger().error('Failed to patch http agent', error) + getLogger().error('Failed to patch http agent %O', error) } } diff --git a/packages/core/src/codewhisperer/client/codewhisperer.ts b/packages/core/src/codewhisperer/client/codewhisperer.ts index 5104ef7ede0..02f8a5f2f52 100644 --- a/packages/core/src/codewhisperer/client/codewhisperer.ts +++ b/packages/core/src/codewhisperer/client/codewhisperer.ts @@ -90,6 +90,7 @@ export type CreateCodeScanResponse = | CodeWhispererUserClient.StartCodeAnalysisResponse export type Import = CodeWhispererUserClient.Import export type Imports = CodeWhispererUserClient.Imports + export class DefaultCodeWhispererClient { private async createSdkClient(): Promise { const isOptedOut = CodeWhispererSettings.instance.isOptoutEnabled() @@ -131,7 +132,7 @@ export class DefaultCodeWhispererClient { )) as CodeWhispererClient } - async createUserSdkClient(): Promise { + async createUserSdkClient(maxRetries?: number): Promise { const isOptedOut = CodeWhispererSettings.instance.isOptoutEnabled() session.setFetchCredentialStart() const bearerToken = await AuthUtil.instance.getBearerToken() @@ -143,6 +144,7 @@ export class DefaultCodeWhispererClient { apiConfig: userApiConfig, region: cwsprConfig.region, endpoint: cwsprConfig.endpoint, + maxRetries: maxRetries, credentials: new Credentials({ accessKeyId: 'xxx', secretAccessKey: 'xxx' }), onRequestSetup: [ (req) => { @@ -292,7 +294,8 @@ export class DefaultCodeWhispererClient { public async codeModernizerGetCodeTransformation( request: CodeWhispererUserClient.GetTransformationRequest ): Promise> { - return (await this.createUserSdkClient()).getTransformation(request).promise() + // instead of the default of 3 retries, use 8 retries for this API which is polled every 5 seconds + return (await this.createUserSdkClient(8)).getTransformation(request).promise() } /** @@ -317,6 +320,30 @@ export class DefaultCodeWhispererClient { ): Promise> { return (await this.createUserSdkClient()).getTransformationPlan(request).promise() } + + public async startCodeFixJob( + request: CodeWhispererUserClient.StartCodeFixJobRequest + ): Promise> { + return (await this.createUserSdkClient()).startCodeFixJob(request).promise() + } + + public async getCodeFixJob( + request: CodeWhispererUserClient.GetCodeFixJobRequest + ): Promise> { + return (await this.createUserSdkClient()).getCodeFixJob(request).promise() + } + + public async startTestGeneration( + request: CodeWhispererUserClient.StartTestGenerationRequest + ): Promise> { + return (await this.createUserSdkClient()).startTestGeneration(request).promise() + } + + public async getTestGeneration( + request: CodeWhispererUserClient.GetTestGenerationRequest + ): Promise> { + return (await this.createUserSdkClient()).getTestGeneration(request).promise() + } } export const codeWhispererClient = new DefaultCodeWhispererClient() diff --git a/packages/core/src/codewhisperer/client/user-service-2.json b/packages/core/src/codewhisperer/client/user-service-2.json index bda8d16922d..123160fb0b3 100644 --- a/packages/core/src/codewhisperer/client/user-service-2.json +++ b/packages/core/src/codewhisperer/client/user-service-2.json @@ -39,6 +39,7 @@ "output": { "shape": "CreateTaskAssistConversationResponse" }, "errors": [ { "shape": "ThrottlingException" }, + { "shape": "ServiceQuotaExceededException" }, { "shape": "InternalServerException" }, { "shape": "ValidationException" }, { "shape": "AccessDeniedException" } @@ -56,6 +57,7 @@ "errors": [ { "shape": "ThrottlingException" }, { "shape": "ConflictException" }, + { "shape": "ServiceQuotaExceededException" }, { "shape": "ResourceNotFoundException" }, { "shape": "InternalServerException" }, { "shape": "ValidationException" }, @@ -114,6 +116,22 @@ ], "documentation": "

Gets the metadata of a code analysis job.

" }, + "GetCodeFixJob": { + "name": "GetCodeFixJob", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { "shape": "GetCodeFixJobRequest" }, + "output": { "shape": "GetCodeFixJobResponse" }, + "errors": [ + { "shape": "ThrottlingException" }, + { "shape": "ResourceNotFoundException" }, + { "shape": "InternalServerException" }, + { "shape": "ValidationException" }, + { "shape": "AccessDeniedException" } + ] + }, "GetTaskAssistCodeGeneration": { "name": "GetTaskAssistCodeGeneration", "http": { @@ -132,6 +150,22 @@ ], "documentation": "

API to get status of task assist code generation.

" }, + "GetTestGeneration": { + "name": "GetTestGeneration", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { "shape": "GetTestGenerationRequest" }, + "output": { "shape": "GetTestGenerationResponse" }, + "errors": [ + { "shape": "ThrottlingException" }, + { "shape": "InternalServerException" }, + { "shape": "ValidationException" }, + { "shape": "AccessDeniedException" } + ], + "documentation": "

API to get test generation job.

" + }, "GetTransformation": { "name": "GetTransformation", "http": { @@ -267,6 +301,21 @@ "documentation": "

Starts a code analysis job

", "idempotent": true }, + "StartCodeFixJob": { + "name": "StartCodeFixJob", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { "shape": "StartCodeFixJobRequest" }, + "output": { "shape": "StartCodeFixJobResponse" }, + "errors": [ + { "shape": "ThrottlingException" }, + { "shape": "InternalServerException" }, + { "shape": "ValidationException" }, + { "shape": "AccessDeniedException" } + ] + }, "StartTaskAssistCodeGeneration": { "name": "StartTaskAssistCodeGeneration", "http": { @@ -278,6 +327,7 @@ "errors": [ { "shape": "ThrottlingException" }, { "shape": "ConflictException" }, + { "shape": "ServiceQuotaExceededException" }, { "shape": "ResourceNotFoundException" }, { "shape": "InternalServerException" }, { "shape": "ValidationException" }, @@ -285,6 +335,24 @@ ], "documentation": "

API to start task assist code generation.

" }, + "StartTestGeneration": { + "name": "StartTestGeneration", + "http": { + "method": "POST", + "requestUri": "/" + }, + "input": { "shape": "StartTestGenerationRequest" }, + "output": { "shape": "StartTestGenerationResponse" }, + "errors": [ + { "shape": "ThrottlingException" }, + { "shape": "ConflictException" }, + { "shape": "InternalServerException" }, + { "shape": "ValidationException" }, + { "shape": "AccessDeniedException" } + ], + "documentation": "

API to start test generation.

", + "idempotent": true + }, "StartTransformation": { "name": "StartTransformation", "http": { @@ -336,6 +404,53 @@ "documentation": "

Reason for AccessDeniedException

", "enum": ["UNAUTHORIZED_CUSTOMIZATION_RESOURCE_ACCESS"] }, + "AppStudioState": { + "type": "structure", + "required": ["namespace", "propertyName", "propertyContext"], + "members": { + "namespace": { + "shape": "AppStudioStateNamespaceString", + "documentation": "

The namespace of the context. Examples: 'ui.Button', 'ui.Table.DataSource', 'ui.Table.RowActions.Button', 'logic.invokeAWS', 'logic.JavaScript'

" + }, + "propertyName": { + "shape": "AppStudioStatePropertyNameString", + "documentation": "

The name of the property. Examples: 'visibility', 'disability', 'value', 'code'

" + }, + "propertyValue": { + "shape": "AppStudioStatePropertyValueString", + "documentation": "

The value of the property.

" + }, + "propertyContext": { + "shape": "AppStudioStatePropertyContextString", + "documentation": "

Context about how the property is used

" + } + }, + "documentation": "

Description of a user's context when they are calling Q Chat from AppStudio

" + }, + "AppStudioStateNamespaceString": { + "type": "string", + "max": 1024, + "min": 1, + "sensitive": true + }, + "AppStudioStatePropertyContextString": { + "type": "string", + "max": 1024, + "min": 1, + "sensitive": true + }, + "AppStudioStatePropertyNameString": { + "type": "string", + "max": 1024, + "min": 1, + "sensitive": true + }, + "AppStudioStatePropertyValueString": { + "type": "string", + "max": 10240, + "min": 0, + "sensitive": true + }, "ArtifactId": { "type": "string", "max": 126, @@ -432,7 +547,8 @@ "acceptedCharacterCount": { "shape": "Integer" }, "acceptedLineCount": { "shape": "Integer" }, "acceptedSnippetHasReference": { "shape": "Boolean" }, - "hasProjectLevelContext": { "shape": "Boolean" } + "hasProjectLevelContext": { "shape": "Boolean" }, + "userIntent": { "shape": "UserIntent" } } }, "ChatInteractWithMessageEventInteractionTargetString": { @@ -466,7 +582,7 @@ "ChatTriggerType": { "type": "string", "documentation": "

Trigger Reason for Chat

", - "enum": ["MANUAL", "DIAGNOSTIC"] + "enum": ["MANUAL", "DIAGNOSTIC", "INLINE_CHAT"] }, "ChatUserModificationEvent": { "type": "structure", @@ -508,7 +624,53 @@ "acceptedCharacterCount": { "shape": "PrimitiveInteger" }, "totalCharacterCount": { "shape": "PrimitiveInteger" }, "timestamp": { "shape": "Timestamp" }, - "unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" } + "unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" }, + "totalNewCodeCharacterCount": { "shape": "PrimitiveInteger" }, + "totalNewCodeLineCount": { "shape": "PrimitiveInteger" } + } + }, + "CodeFixAcceptanceEvent": { + "type": "structure", + "required": ["jobId"], + "members": { + "jobId": { "shape": "String" }, + "ruleId": { "shape": "String" }, + "detectorId": { "shape": "String" }, + "findingId": { "shape": "String" }, + "programmingLanguage": { "shape": "ProgrammingLanguage" }, + "linesOfCodeAccepted": { "shape": "Integer" }, + "charsOfCodeAccepted": { "shape": "Integer" } + } + }, + "CodeFixGenerationEvent": { + "type": "structure", + "required": ["jobId"], + "members": { + "jobId": { "shape": "String" }, + "ruleId": { "shape": "String" }, + "detectorId": { "shape": "String" }, + "findingId": { "shape": "String" }, + "programmingLanguage": { "shape": "ProgrammingLanguage" }, + "linesOfCodeGenerated": { "shape": "Integer" }, + "charsOfCodeGenerated": { "shape": "Integer" } + } + }, + "CodeFixJobStatus": { + "type": "string", + "enum": ["Succeeded", "InProgress", "Failed"] + }, + "CodeFixName": { + "type": "string", + "documentation": "

Code fix name

", + "max": 128, + "min": 1, + "pattern": "[a-zA-Z0-9-_$:.]*" + }, + "CodeFixUploadContext": { + "type": "structure", + "required": ["codeFixName"], + "members": { + "codeFixName": { "shape": "CodeFixName" } } }, "CodeGenerationId": { @@ -544,8 +706,21 @@ "members": { "programmingLanguage": { "shape": "ProgrammingLanguage" }, "codeScanJobId": { "shape": "CodeScanJobId" }, - "timestamp": { "shape": "Timestamp" } - } + "timestamp": { "shape": "Timestamp" }, + "codeAnalysisScope": { "shape": "CodeAnalysisScope" } + }, + "documentation": "

Published when a security scan or code review starts

" + }, + "CodeScanFailedEvent": { + "type": "structure", + "required": ["programmingLanguage", "codeScanJobId", "timestamp"], + "members": { + "programmingLanguage": { "shape": "ProgrammingLanguage" }, + "codeScanJobId": { "shape": "CodeScanJobId" }, + "timestamp": { "shape": "Timestamp" }, + "codeAnalysisScope": { "shape": "CodeAnalysisScope" } + }, + "documentation": "

Published when a security scan or code review fails

" }, "CodeScanJobId": { "type": "string", @@ -578,6 +753,18 @@ "documentation": "

Code Scan Remediations Interaction Type

", "enum": ["CODESCAN_ISSUE_HOVER", "CODESCAN_ISSUE_APPLY_FIX", "CODESCAN_ISSUE_VIEW_DETAILS"] }, + "CodeScanSucceededEvent": { + "type": "structure", + "required": ["programmingLanguage", "codeScanJobId", "timestamp", "numberOfFindings"], + "members": { + "programmingLanguage": { "shape": "ProgrammingLanguage" }, + "codeScanJobId": { "shape": "CodeScanJobId" }, + "timestamp": { "shape": "Timestamp" }, + "numberOfFindings": { "shape": "PrimitiveInteger" }, + "codeAnalysisScope": { "shape": "CodeAnalysisScope" } + }, + "documentation": "

Published when a security scan or code review completes successfully

" + }, "Completion": { "type": "structure", "required": ["content"], @@ -607,11 +794,29 @@ "type": "structure", "required": ["message"], "members": { - "message": { "shape": "String" } + "message": { "shape": "String" }, + "reason": { "shape": "ConflictExceptionReason" } }, "documentation": "

This exception is thrown when the action to perform could not be completed because the resource is in a conflicting state.

", "exception": true }, + "ConflictExceptionReason": { + "type": "string", + "documentation": "

Reason for ConflictException

", + "enum": ["CUSTOMER_KMS_KEY_INVALID_KEY_POLICY", "CUSTOMER_KMS_KEY_DISABLED", "MISMATCHED_KMS_KEY"] + }, + "ConsoleState": { + "type": "structure", + "members": { + "region": { "shape": "String" }, + "consoleUrl": { "shape": "SensitiveString" }, + "serviceId": { "shape": "String" }, + "serviceConsolePage": { "shape": "String" }, + "serviceSubconsolePage": { "shape": "String" }, + "taskName": { "shape": "SensitiveString" } + }, + "documentation": "

Information about the state of the AWS management console page from which the user is calling

" + }, "ContentChecksumType": { "type": "string", "enum": ["SHA_256"] @@ -673,7 +878,8 @@ "contentLength": { "shape": "CreateUploadUrlRequestContentLengthLong" }, "artifactType": { "shape": "ArtifactType" }, "uploadIntent": { "shape": "UploadIntent" }, - "uploadContext": { "shape": "UploadContext" } + "uploadContext": { "shape": "UploadContext" }, + "uploadId": { "shape": "UploadId" } } }, "CreateUploadUrlRequestContentChecksumString": { @@ -700,9 +906,7 @@ "uploadId": { "shape": "UploadId" }, "uploadUrl": { "shape": "PreSignedUrl" }, "kmsKeyArn": { "shape": "ResourceArn" }, - "requestHeaders": { - "shape": "RequestHeaders" - } + "requestHeaders": { "shape": "RequestHeaders" } } }, "CursorState": { @@ -812,6 +1016,35 @@ "min": 1, "pattern": "[-a-zA-Z0-9._]*" }, + "DocGenerationEvent": { + "type": "structure", + "required": ["conversationId"], + "members": { + "conversationId": { "shape": "ConversationId" }, + "numberOfAddChars": { "shape": "PrimitiveInteger" }, + "numberOfAddLines": { "shape": "PrimitiveInteger" }, + "numberOfAddFiles": { "shape": "PrimitiveInteger" }, + "userDecision": { "shape": "DocGenerationUserDecision" }, + "interactionType": { "shape": "DocGenerationInteractionType" }, + "userIdentity": { "shape": "String" }, + "numberOfNavigation": { "shape": "PrimitiveInteger" }, + "folderLevel": { "shape": "DocGenerationFolderLevel" } + } + }, + "DocGenerationFolderLevel": { + "type": "string", + "documentation": "

Doc Generation Folder Level

", + "enum": ["SUB_FOLDER", "ENTIRE_WORKSPACE"] + }, + "DocGenerationInteractionType": { + "type": "string", + "documentation": "

Doc Generation Interaction Type

", + "enum": ["GENERATE_README", "UPDATE_README", "EDIT_README"] + }, + "DocGenerationUserDecision": { + "type": "string", + "enum": ["ACCEPT", "REJECT"] + }, "DocumentSymbol": { "type": "structure", "required": ["name", "type"], @@ -846,6 +1079,24 @@ "max": 1000, "min": 0 }, + "DocumentationIntentContext": { + "type": "structure", + "required": ["type"], + "members": { + "scope": { "shape": "DocumentationIntentContextScopeString" }, + "type": { "shape": "DocumentationType" } + } + }, + "DocumentationIntentContextScopeString": { + "type": "string", + "max": 4096, + "min": 1, + "sensitive": true + }, + "DocumentationType": { + "type": "string", + "enum": ["README"] + }, "Double": { "type": "double", "box": true @@ -860,6 +1111,14 @@ "cursorState": { "shape": "CursorState", "documentation": "

Position of the cursor

" + }, + "relevantDocuments": { + "shape": "RelevantDocumentList", + "documentation": "

Represents IDE provided relevant files

" + }, + "useRelevantDocuments": { + "shape": "Boolean", + "documentation": "

Whether service should use relevant document in prompt

" } }, "documentation": "

Represents the state of an Editor

" @@ -878,6 +1137,10 @@ "environmentVariables": { "shape": "EnvironmentVariables", "documentation": "

The environment variables set in the current environment

" + }, + "timezoneOffset": { + "shape": "EnvStateTimezoneOffsetInteger", + "documentation": "

Local timezone offset of the client. For more information, see documentation https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset

" } }, "documentation": "

State related to the user's environment

" @@ -894,6 +1157,12 @@ "min": 1, "pattern": "(macos|linux|windows)" }, + "EnvStateTimezoneOffsetInteger": { + "type": "integer", + "box": true, + "max": 1440, + "min": -1440 + }, "EnvironmentVariable": { "type": "structure", "members": { @@ -927,6 +1196,51 @@ "max": 100, "min": 0 }, + "FeatureDevCodeAcceptanceEvent": { + "type": "structure", + "required": ["conversationId", "linesOfCodeAccepted", "charactersOfCodeAccepted"], + "members": { + "conversationId": { "shape": "ConversationId" }, + "linesOfCodeAccepted": { "shape": "FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger" }, + "charactersOfCodeAccepted": { "shape": "FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger" }, + "programmingLanguage": { "shape": "ProgrammingLanguage" } + } + }, + "FeatureDevCodeAcceptanceEventCharactersOfCodeAcceptedInteger": { + "type": "integer", + "min": 0 + }, + "FeatureDevCodeAcceptanceEventLinesOfCodeAcceptedInteger": { + "type": "integer", + "min": 0 + }, + "FeatureDevCodeGenerationEvent": { + "type": "structure", + "required": ["conversationId", "linesOfCodeGenerated", "charactersOfCodeGenerated"], + "members": { + "conversationId": { "shape": "ConversationId" }, + "linesOfCodeGenerated": { "shape": "FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger" }, + "charactersOfCodeGenerated": { + "shape": "FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger" + }, + "programmingLanguage": { "shape": "ProgrammingLanguage" } + } + }, + "FeatureDevCodeGenerationEventCharactersOfCodeGeneratedInteger": { + "type": "integer", + "min": 0 + }, + "FeatureDevCodeGenerationEventLinesOfCodeGeneratedInteger": { + "type": "integer", + "min": 0 + }, + "FeatureDevEvent": { + "type": "structure", + "required": ["conversationId"], + "members": { + "conversationId": { "shape": "ConversationId" } + } + }, "FeatureEvaluation": { "type": "structure", "required": ["feature", "variation", "value"], @@ -1029,7 +1343,8 @@ "supplementalContexts": { "shape": "SupplementalContextList" }, "customizationArn": { "shape": "CustomizationArn" }, "optOutPreference": { "shape": "OptOutPreference" }, - "userContext": { "shape": "UserContext" } + "userContext": { "shape": "UserContext" }, + "profileArn": { "shape": "ProfileArn" } } }, "GenerateCompletionsRequestMaxResultsInteger": { @@ -1072,6 +1387,26 @@ "errorMessage": { "shape": "SensitiveString" } } }, + "GetCodeFixJobRequest": { + "type": "structure", + "required": ["jobId"], + "members": { + "jobId": { "shape": "GetCodeFixJobRequestJobIdString" } + } + }, + "GetCodeFixJobRequestJobIdString": { + "type": "string", + "max": 256, + "min": 1, + "pattern": ".*[A-Za-z0-9-:]+.*" + }, + "GetCodeFixJobResponse": { + "type": "structure", + "members": { + "jobStatus": { "shape": "CodeFixJobStatus" }, + "suggestedFix": { "shape": "SuggestedFix" } + } + }, "GetTaskAssistCodeGenerationRequest": { "type": "structure", "required": ["conversationId", "codeGenerationId"], @@ -1093,6 +1428,22 @@ }, "documentation": "

Response for getting task assist code generation status.

" }, + "GetTestGenerationRequest": { + "type": "structure", + "required": ["testGenerationJobGroupName", "testGenerationJobId"], + "members": { + "testGenerationJobGroupName": { "shape": "TestGenerationJobGroupName" }, + "testGenerationJobId": { "shape": "UUID" } + }, + "documentation": "

Structure to represent get test generation request.

" + }, + "GetTestGenerationResponse": { + "type": "structure", + "members": { + "testGenerationJob": { "shape": "TestGenerationJob" } + }, + "documentation": "

Structure to represent get test generation response.

" + }, "GetTransformationPlanRequest": { "type": "structure", "required": ["transformationJobId"], @@ -1143,7 +1494,7 @@ }, "IdeCategory": { "type": "string", - "enum": ["JETBRAINS", "VSCODE", "CLI"], + "enum": ["JETBRAINS", "VSCODE", "CLI", "JUPYTER_MD", "JUPYTER_SM", "ECLIPSE", "VISUAL_STUDIO"], "max": 64, "min": 1 }, @@ -1170,10 +1521,44 @@ "max": 10, "min": 0 }, + "InlineChatEvent": { + "type": "structure", + "required": ["requestId", "timestamp"], + "members": { + "requestId": { "shape": "UUID" }, + "timestamp": { "shape": "Timestamp" }, + "inputLength": { "shape": "PrimitiveInteger" }, + "numSelectedLines": { "shape": "PrimitiveInteger" }, + "numSuggestionAddChars": { "shape": "PrimitiveInteger" }, + "numSuggestionAddLines": { "shape": "PrimitiveInteger" }, + "numSuggestionDelChars": { "shape": "PrimitiveInteger" }, + "numSuggestionDelLines": { "shape": "PrimitiveInteger" }, + "codeIntent": { "shape": "Boolean" }, + "userDecision": { "shape": "InlineChatUserDecision" }, + "responseStartLatency": { "shape": "Double" }, + "responseEndLatency": { "shape": "Double" }, + "programmingLanguage": { "shape": "ProgrammingLanguage" } + } + }, + "InlineChatUserDecision": { + "type": "string", + "enum": ["ACCEPT", "REJECT", "DISMISS"] + }, "Integer": { "type": "integer", "box": true }, + "Intent": { + "type": "string", + "enum": ["DEV", "DOC"] + }, + "IntentContext": { + "type": "structure", + "members": { + "documentation": { "shape": "DocumentationIntentContext" } + }, + "union": true + }, "InternalServerException": { "type": "structure", "required": ["message"], @@ -1185,6 +1570,10 @@ "fault": true, "retryable": { "throttling": false } }, + "LineRangeList": { + "type": "list", + "member": { "shape": "Range" } + }, "ListAvailableCustomizationsRequest": { "type": "structure", "members": { @@ -1313,6 +1702,12 @@ "sensitive": true }, "PrimitiveInteger": { "type": "integer" }, + "ProfileArn": { + "type": "string", + "max": 950, + "min": 0, + "pattern": "arn:aws:codewhisperer:[-.a-z0-9]{1,63}:\\d{12}:profile/([a-zA-Z0-9]){12}" + }, "ProgrammingLanguage": { "type": "structure", "required": ["languageName"], @@ -1325,7 +1720,7 @@ "type": "string", "max": 128, "min": 1, - "pattern": "(python|javascript|java|csharp|typescript|c|cpp|go|kotlin|php|ruby|rust|scala|shell|sql|json|yaml|vue|tf|tsx|jsx)" + "pattern": "(python|javascript|java|csharp|typescript|c|cpp|go|kotlin|php|ruby|rust|scala|shell|sql|json|yaml|vue|tf|tsx|jsx|plaintext|systemverilog|dart|lua|swift|powershell|r)" }, "ProgressUpdates": { "type": "list", @@ -1401,11 +1796,46 @@ "max": 10, "min": 0 }, - "ResourceArn": { + "RelevantDocumentList": { + "type": "list", + "member": { "shape": "RelevantTextDocument" }, + "max": 5, + "min": 0 + }, + "RelevantTextDocument": { + "type": "structure", + "required": ["relativeFilePath"], + "members": { + "relativeFilePath": { + "shape": "RelevantTextDocumentRelativeFilePathString", + "documentation": "

Filepath relative to the root of the workspace

" + }, + "programmingLanguage": { + "shape": "ProgrammingLanguage", + "documentation": "

The text document's language identifier.

" + }, + "text": { + "shape": "RelevantTextDocumentTextString", + "documentation": "

Content of the text document

" + }, + "documentSymbols": { + "shape": "DocumentSymbols", + "documentation": "

DocumentSymbols parsed from a text document

" + } + }, + "documentation": "

Represents an IDE retrieved relevant Text Document / File

" + }, + "RelevantTextDocumentRelativeFilePathString": { "type": "string", - "max": 1224, + "max": 4096, + "min": 1, + "sensitive": true + }, + "RelevantTextDocumentTextString": { + "type": "string", + "max": 10240, "min": 0, - "pattern": "arn:([-.a-z0-9]{1,63}:){2}([-.a-z0-9]{0,63}:){2}([a-zA-Z0-9-_:/]){1,1023}" + "sensitive": true }, "RequestHeaderKey": { "type": "string", @@ -1419,14 +1849,17 @@ }, "RequestHeaders": { "type": "map", - "key": { - "shape": "RequestHeaderKey" - }, - "value": { - "shape": "RequestHeaderValue" - }, + "key": { "shape": "RequestHeaderKey" }, + "value": { "shape": "RequestHeaderValue" }, "max": 16, - "min": 1 + "min": 1, + "sensitive": true + }, + "ResourceArn": { + "type": "string", + "max": 1224, + "min": 0, + "pattern": "arn:([-.a-z0-9]{1,63}:){2}([-.a-z0-9]{0,63}:){2}([a-zA-Z0-9-_:/]){1,1023}" }, "ResourceNotFoundException": { "type": "structure", @@ -1495,7 +1928,8 @@ }, "telemetryEvent": { "shape": "TelemetryEvent" }, "optOutPreference": { "shape": "OptOutPreference" }, - "userContext": { "shape": "UserContext" } + "userContext": { "shape": "UserContext" }, + "profileArn": { "shape": "ProfileArn" } } }, "SendTelemetryEventResponse": { @@ -1506,6 +1940,15 @@ "type": "string", "sensitive": true }, + "ServiceQuotaExceededException": { + "type": "structure", + "required": ["message"], + "members": { + "message": { "shape": "String" } + }, + "documentation": "

This exception is thrown when request was denied due to caller exceeding their usage limits

", + "exception": true + }, "ShellHistory": { "type": "list", "member": { "shape": "ShellHistoryEntry" }, @@ -1609,11 +2052,11 @@ "members": { "artifacts": { "shape": "ArtifactMap" }, "programmingLanguage": { "shape": "ProgrammingLanguage" }, - "scope": { "shape": "CodeAnalysisScope" }, "clientToken": { "shape": "StartCodeAnalysisRequestClientTokenString", "idempotencyToken": true }, + "scope": { "shape": "CodeAnalysisScope" }, "codeScanName": { "shape": "CodeScanName" } } }, @@ -1636,12 +2079,53 @@ "max": 256, "min": 1 }, + "StartCodeFixJobRequest": { + "type": "structure", + "required": ["snippetRange", "uploadId"], + "members": { + "snippetRange": { "shape": "Range" }, + "uploadId": { "shape": "UploadId" }, + "description": { "shape": "StartCodeFixJobRequestDescriptionString" }, + "ruleId": { "shape": "StartCodeFixJobRequestRuleIdString" }, + "codeFixName": { "shape": "CodeFixName" } + } + }, + "StartCodeFixJobRequestDescriptionString": { + "type": "string", + "max": 2000, + "min": 1, + "sensitive": true + }, + "StartCodeFixJobRequestRuleIdString": { + "type": "string", + "max": 256, + "min": 1, + "pattern": ".*[A-Za-z0-9-]+.*" + }, + "StartCodeFixJobResponse": { + "type": "structure", + "members": { + "jobId": { "shape": "StartCodeFixJobResponseJobIdString" }, + "status": { "shape": "CodeFixJobStatus" } + } + }, + "StartCodeFixJobResponseJobIdString": { + "type": "string", + "max": 256, + "min": 1, + "pattern": ".*[A-Za-z0-9-:]+.*" + }, "StartTaskAssistCodeGenerationRequest": { "type": "structure", "required": ["conversationState", "workspaceState"], "members": { "conversationState": { "shape": "ConversationState" }, - "workspaceState": { "shape": "WorkspaceState" } + "workspaceState": { "shape": "WorkspaceState" }, + "taskAssistPlan": { "shape": "TaskAssistPlan" }, + "codeGenerationId": { "shape": "CodeGenerationId" }, + "currentCodeGenerationId": { "shape": "CodeGenerationId" }, + "intent": { "shape": "Intent" }, + "intentContext": { "shape": "IntentContext" } }, "documentation": "

Structure to represent start code generation request.

" }, @@ -1654,6 +2138,42 @@ }, "documentation": "

Structure to represent start code generation response.

" }, + "StartTestGenerationRequest": { + "type": "structure", + "required": ["uploadId", "targetCodeList", "userInput"], + "members": { + "uploadId": { "shape": "UploadId" }, + "targetCodeList": { "shape": "TargetCodeList" }, + "userInput": { + "shape": "StartTestGenerationRequestUserInputString", + "documentation": "

The content of user input.

" + }, + "testGenerationJobGroupName": { "shape": "TestGenerationJobGroupName" }, + "clientToken": { + "shape": "StartTestGenerationRequestClientTokenString", + "idempotencyToken": true + } + }, + "documentation": "

Structure to represent test generation request.

" + }, + "StartTestGenerationRequestClientTokenString": { + "type": "string", + "max": 256, + "min": 1 + }, + "StartTestGenerationRequestUserInputString": { + "type": "string", + "max": 4096, + "min": 0, + "sensitive": true + }, + "StartTestGenerationResponse": { + "type": "structure", + "members": { + "testGenerationJob": { "shape": "TestGenerationJob" } + }, + "documentation": "

Structure to represent code transformation response.

" + }, "StartTransformationRequest": { "type": "structure", "required": ["workspaceState", "transformationSpec"], @@ -1693,9 +2213,29 @@ "documentation": "

Structure to represent stop code transformation response.

" }, "String": { "type": "string" }, + "SuggestedFix": { + "type": "structure", + "members": { + "codeDiff": { "shape": "SuggestedFixCodeDiffString" }, + "description": { "shape": "SuggestedFixDescriptionString" }, + "references": { "shape": "References" } + } + }, + "SuggestedFixCodeDiffString": { + "type": "string", + "max": 200000, + "min": 0, + "sensitive": true + }, + "SuggestedFixDescriptionString": { + "type": "string", + "max": 2000, + "min": 1, + "sensitive": true + }, "SuggestionState": { "type": "string", - "enum": ["ACCEPT", "REJECT", "DISCARD", "EMPTY"] + "enum": ["ACCEPT", "REJECT", "DISCARD", "EMPTY", "MERGE"] }, "SupplementalContext": { "type": "structure", @@ -1770,6 +2310,85 @@ "type": "string", "enum": ["DECLARATION", "USAGE"] }, + "TargetCode": { + "type": "structure", + "required": ["relativeTargetPath"], + "members": { + "relativeTargetPath": { + "shape": "TargetCodeRelativeTargetPathString", + "documentation": "

The file path relative to the root of the workspace, could be a single file or a folder.

" + }, + "targetLineRangeList": { "shape": "LineRangeList" } + } + }, + "TargetCodeList": { + "type": "list", + "member": { "shape": "TargetCode" }, + "min": 1 + }, + "TargetCodeRelativeTargetPathString": { + "type": "string", + "max": 4096, + "min": 1, + "sensitive": true + }, + "TaskAssistPlan": { + "type": "list", + "member": { "shape": "TaskAssistPlanStep" }, + "min": 0 + }, + "TaskAssistPlanStep": { + "type": "structure", + "required": ["filePath", "description"], + "members": { + "filePath": { + "shape": "TaskAssistPlanStepFilePathString", + "documentation": "

File path on which the step is working on.

" + }, + "description": { + "shape": "TaskAssistPlanStepDescriptionString", + "documentation": "

Description on the step.

" + }, + "startLine": { + "shape": "TaskAssistPlanStepStartLineInteger", + "documentation": "

Start line number of the related changes.

" + }, + "endLine": { + "shape": "TaskAssistPlanStepEndLineInteger", + "documentation": "

End line number of the related changes.

" + }, + "action": { + "shape": "TaskAssistPlanStepAction", + "documentation": "

Type of the action.

" + } + }, + "documentation": "

Structured plan step for a task assist plan.

" + }, + "TaskAssistPlanStepAction": { + "type": "string", + "documentation": "

Action for task assist plan step

", + "enum": ["MODIFY", "CREATE", "DELETE", "UNKNOWN"] + }, + "TaskAssistPlanStepDescriptionString": { + "type": "string", + "max": 1024, + "min": 1 + }, + "TaskAssistPlanStepEndLineInteger": { + "type": "integer", + "box": true, + "min": 0 + }, + "TaskAssistPlanStepFilePathString": { + "type": "string", + "max": 1024, + "min": 1 + }, + "TaskAssistPlanStepStartLineInteger": { + "type": "integer", + "box": true, + "min": 0 + }, "TaskAssistPlanningUploadContext": { "type": "structure", "required": ["conversationId"], @@ -1784,12 +2403,23 @@ "codeCoverageEvent": { "shape": "CodeCoverageEvent" }, "userModificationEvent": { "shape": "UserModificationEvent" }, "codeScanEvent": { "shape": "CodeScanEvent" }, + "codeScanSucceededEvent": { "shape": "CodeScanSucceededEvent" }, + "codeScanFailedEvent": { "shape": "CodeScanFailedEvent" }, "codeScanRemediationsEvent": { "shape": "CodeScanRemediationsEvent" }, + "codeFixGenerationEvent": { "shape": "CodeFixGenerationEvent" }, + "codeFixAcceptanceEvent": { "shape": "CodeFixAcceptanceEvent" }, "metricData": { "shape": "MetricData" }, "chatAddMessageEvent": { "shape": "ChatAddMessageEvent" }, "chatInteractWithMessageEvent": { "shape": "ChatInteractWithMessageEvent" }, "chatUserModificationEvent": { "shape": "ChatUserModificationEvent" }, - "terminalUserInteractionEvent": { "shape": "TerminalUserInteractionEvent" } + "terminalUserInteractionEvent": { "shape": "TerminalUserInteractionEvent" }, + "featureDevEvent": { "shape": "FeatureDevEvent" }, + "featureDevCodeGenerationEvent": { "shape": "FeatureDevCodeGenerationEvent" }, + "featureDevCodeAcceptanceEvent": { "shape": "FeatureDevCodeAcceptanceEvent" }, + "inlineChatEvent": { "shape": "InlineChatEvent" }, + "transformEvent": { "shape": "TransformEvent" }, + "docGenerationEvent": { "shape": "DocGenerationEvent" }, + "testGenerationEvent": { "shape": "TestGenerationEvent" } }, "union": true }, @@ -1812,6 +2442,53 @@ "documentation": "

CodeWhisperer terminal Interaction Type

", "enum": ["CODEWHISPERER_TERMINAL_TRANSLATION_ACTION", "CODEWHISPERER_TERMINAL_COMPLETION_INSERTED"] }, + "TestGenerationEvent": { + "type": "structure", + "required": ["jobId", "groupName"], + "members": { + "jobId": { "shape": "UUID" }, + "groupName": { "shape": "TestGenerationJobGroupName" }, + "timestamp": { "shape": "Timestamp" }, + "ideCategory": { "shape": "IdeCategory" }, + "programmingLanguage": { "shape": "ProgrammingLanguage" }, + "numberOfUnitTestCasesGenerated": { "shape": "Integer" }, + "numberOfUnitTestCasesAccepted": { "shape": "Integer" }, + "linesOfCodeGenerated": { "shape": "Integer" }, + "linesOfCodeAccepted": { "shape": "Integer" }, + "charsOfCodeGenerated": { "shape": "Integer" }, + "charsOfCodeAccepted": { "shape": "Integer" } + } + }, + "TestGenerationJob": { + "type": "structure", + "required": ["testGenerationJobId", "testGenerationJobGroupName", "status", "creationTime"], + "members": { + "testGenerationJobId": { "shape": "UUID" }, + "testGenerationJobGroupName": { "shape": "TestGenerationJobGroupName" }, + "status": { "shape": "TestGenerationJobStatus" }, + "shortAnswer": { "shape": "SensitiveString" }, + "creationTime": { "shape": "Timestamp" }, + "progressRate": { "shape": "TestGenerationJobProgressRateInteger" } + }, + "documentation": "

Represents a test generation job

" + }, + "TestGenerationJobGroupName": { + "type": "string", + "documentation": "

Test generation job group name

", + "max": 128, + "min": 1, + "pattern": "[a-zA-Z0-9-_]+" + }, + "TestGenerationJobProgressRateInteger": { + "type": "integer", + "box": true, + "max": 100, + "min": 0 + }, + "TestGenerationJobStatus": { + "type": "string", + "enum": ["IN_PROGRESS", "FAILED", "COMPLETED"] + }, "TextDocument": { "type": "structure", "required": ["relativeFilePath"], @@ -1891,34 +2568,22 @@ "retryable": { "throttling": true } }, "Timestamp": { "type": "timestamp" }, + "TransformEvent": { + "type": "structure", + "required": ["jobId"], + "members": { + "jobId": { "shape": "TransformationJobId" }, + "timestamp": { "shape": "Timestamp" }, + "ideCategory": { "shape": "IdeCategory" }, + "programmingLanguage": { "shape": "ProgrammingLanguage" }, + "linesOfCodeChanged": { "shape": "Integer" }, + "charsOfCodeChanged": { "shape": "Integer" }, + "linesOfCodeSubmitted": { "shape": "Integer" } + } + }, "TransformationDotNetRuntimeEnv": { "type": "string", - "enum": [ - "NET_FRAMEWORK_V_3_5", - "NET_FRAMEWORK_V_4_0", - "NET_FRAMEWORK_V_4_5", - "NET_FRAMEWORK_V_4_5_1", - "NET_FRAMEWORK_V_4_5_2", - "NET_FRAMEWORK_V_4_6", - "NET_FRAMEWORK_V_4_6_1", - "NET_FRAMEWORK_V_4_6_2", - "NET_FRAMEWORK_V_4_7", - "NET_FRAMEWORK_V_4_7_1", - "NET_FRAMEWORK_V_4_7_2", - "NET_FRAMEWORK_V_4_8", - "NET_FRAMEWORK_V_4_8_1", - "NET_CORE_APP_1_0", - "NET_CORE_APP_1_1", - "NET_CORE_APP_2_0", - "NET_CORE_APP_2_1", - "NET_CORE_APP_2_2", - "NET_CORE_APP_3_0", - "NET_CORE_APP_3_1", - "NET_5_0", - "NET_6_0", - "NET_7_0", - "NET_8_0" - ] + "enum": ["NET_5_0", "NET_6_0", "NET_7_0", "NET_8_0", "NET_9_0", "NET_STANDARD_2_0"] }, "TransformationDownloadArtifact": { "type": "structure", @@ -1929,7 +2594,7 @@ }, "TransformationDownloadArtifactType": { "type": "string", - "enum": ["ClientInstructions", "Logs"] + "enum": ["ClientInstructions", "Logs", "GeneratedCode"] }, "TransformationDownloadArtifacts": { "type": "list", @@ -1962,7 +2627,15 @@ }, "TransformationLanguage": { "type": "string", - "enum": ["JAVA_8", "JAVA_11", "JAVA_17", "C_SHARP"] + "enum": ["JAVA_8", "JAVA_11", "JAVA_17", "C_SHARP", "COBOL", "PL_I", "JCL"] + }, + "TransformationLanguages": { + "type": "list", + "member": { "shape": "TransformationLanguage" } + }, + "TransformationMainframeRuntimeEnv": { + "type": "string", + "enum": ["MAINFRAME"] }, "TransformationOperatingSystemFamily": { "type": "string", @@ -1995,24 +2668,40 @@ }, "TransformationProgressUpdateStatus": { "type": "string", - "enum": ["IN_PROGRESS", "COMPLETED", "FAILED", "PAUSED"] + "enum": ["IN_PROGRESS", "COMPLETED", "FAILED", "PAUSED", "AWAITING_CLIENT_ACTION", "SKIPPED"] + }, + "TransformationProjectArtifactDescriptor": { + "type": "structure", + "members": { + "sourceCodeArtifact": { "shape": "TransformationSourceCodeArtifactDescriptor" } + }, + "union": true }, "TransformationProjectState": { "type": "structure", "members": { "language": { "shape": "TransformationLanguage" }, "runtimeEnv": { "shape": "TransformationRuntimeEnv" }, - "platformConfig": { "shape": "TransformationPlatformConfig" } + "platformConfig": { "shape": "TransformationPlatformConfig" }, + "projectArtifact": { "shape": "TransformationProjectArtifactDescriptor" } } }, "TransformationRuntimeEnv": { "type": "structure", "members": { "java": { "shape": "TransformationJavaRuntimeEnv" }, - "dotNet": { "shape": "TransformationDotNetRuntimeEnv" } + "dotNet": { "shape": "TransformationDotNetRuntimeEnv" }, + "mainframe": { "shape": "TransformationMainframeRuntimeEnv" } }, "union": true }, + "TransformationSourceCodeArtifactDescriptor": { + "type": "structure", + "members": { + "languages": { "shape": "TransformationLanguages" }, + "runtimeEnv": { "shape": "TransformationRuntimeEnv" } + } + }, "TransformationSpec": { "type": "structure", "members": { @@ -2058,7 +2747,7 @@ }, "TransformationStepStatus": { "type": "string", - "enum": ["CREATED", "COMPLETED", "PARTIALLY_COMPLETED", "STOPPED", "FAILED", "PAUSED"] + "enum": ["CREATED", "COMPLETED", "PARTIALLY_COMPLETED", "STOPPED", "FAILED", "PAUSED", "SKIPPED"] }, "TransformationSteps": { "type": "list", @@ -2066,11 +2755,11 @@ }, "TransformationType": { "type": "string", - "enum": ["LANGUAGE_UPGRADE"] + "enum": ["LANGUAGE_UPGRADE", "DOCUMENT_GENERATION"] }, "TransformationUploadArtifactType": { "type": "string", - "enum": ["Dependencies"] + "enum": ["Dependencies", "ClientBuildResult"] }, "TransformationUploadContext": { "type": "structure", @@ -2094,7 +2783,8 @@ "members": { "taskAssistPlanningUploadContext": { "shape": "TaskAssistPlanningUploadContext" }, "transformationUploadContext": { "shape": "TransformationUploadContext" }, - "codeAnalysisUploadContext": { "shape": "CodeAnalysisUploadContext" } + "codeAnalysisUploadContext": { "shape": "CodeAnalysisUploadContext" }, + "codeFixUploadContext": { "shape": "CodeFixUploadContext" } }, "union": true }, @@ -2111,7 +2801,9 @@ "TRANSFORMATION", "TASK_ASSIST_PLANNING", "AUTOMATIC_FILE_SECURITY_SCAN", - "FULL_PROJECT_SECURITY_SCAN" + "FULL_PROJECT_SECURITY_SCAN", + "UNIT_TESTS_GENERATION", + "CODE_FIX_GENERATION" ] }, "UserContext": { @@ -2173,11 +2865,23 @@ }, "envState": { "shape": "EnvState", - "documentation": "

Environment state chat messaage context.

" + "documentation": "

Environment state chat message context.

" + }, + "appStudioContext": { + "shape": "AppStudioState", + "documentation": "

The state of a user's AppStudio UI when sending a message.

" }, "diagnostic": { "shape": "Diagnostic", "documentation": "

Diagnostic chat message context.

" + }, + "consoleState": { + "shape": "ConsoleState", + "documentation": "

Contextual information about the environment from which the user is calling.

" + }, + "userSettings": { + "shape": "UserSettings", + "documentation": "

Settings information, e.g., whether the user has enabled cross-region API calls.

" } }, "documentation": "

Additional Chat message context associated with the Chat Message

" @@ -2192,21 +2896,41 @@ "SHOW_EXAMPLES", "CITE_SOURCES", "EXPLAIN_LINE_BY_LINE", - "EXPLAIN_CODE_SELECTION" + "EXPLAIN_CODE_SELECTION", + "GENERATE_CLOUDFORMATION_TEMPLATE", + "GENERATE_UNIT_TESTS", + "CODE_GENERATION" ] }, "UserModificationEvent": { "type": "structure", - "required": ["sessionId", "requestId", "programmingLanguage", "modificationPercentage", "timestamp"], + "required": [ + "sessionId", + "requestId", + "programmingLanguage", + "modificationPercentage", + "timestamp", + "acceptedCharacterCount", + "unmodifiedAcceptedCharacterCount" + ], "members": { "sessionId": { "shape": "UUID" }, "requestId": { "shape": "UUID" }, "programmingLanguage": { "shape": "ProgrammingLanguage" }, "modificationPercentage": { "shape": "Double" }, "customizationArn": { "shape": "CustomizationArn" }, - "timestamp": { "shape": "Timestamp" } + "timestamp": { "shape": "Timestamp" }, + "acceptedCharacterCount": { "shape": "PrimitiveInteger" }, + "unmodifiedAcceptedCharacterCount": { "shape": "PrimitiveInteger" } } }, + "UserSettings": { + "type": "structure", + "members": { + "hasConsentedToCrossRegionCalls": { "shape": "Boolean" } + }, + "documentation": "

Settings information passed by the Q widget

" + }, "UserTriggerDecisionEvent": { "type": "structure", "required": [ @@ -2230,7 +2954,9 @@ "triggerToResponseLatencyMilliseconds": { "shape": "Double" }, "suggestionReferenceCount": { "shape": "PrimitiveInteger" }, "generatedLine": { "shape": "PrimitiveInteger" }, - "numberOfRecommendations": { "shape": "PrimitiveInteger" } + "numberOfRecommendations": { "shape": "PrimitiveInteger" }, + "perceivedLatencyMilliseconds": { "shape": "Double" }, + "acceptedCharacterCount": { "shape": "PrimitiveInteger" } } }, "ValidationException": { @@ -2246,7 +2972,7 @@ "ValidationExceptionReason": { "type": "string", "documentation": "

Reason for ValidationException

", - "enum": ["INVALID_CONVERSATION_ID", "CONTENT_LENGTH_EXCEEDS_THRESHOLD"] + "enum": ["INVALID_CONVERSATION_ID", "CONTENT_LENGTH_EXCEEDS_THRESHOLD", "INVALID_KMS_GRANT"] }, "WorkspaceState": { "type": "structure", diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts index bf68325dfaa..8ec54d02a2a 100644 --- a/packages/core/src/codewhisperer/commands/basicCommands.ts +++ b/packages/core/src/codewhisperer/commands/basicCommands.ts @@ -9,24 +9,39 @@ import { ExtContext, VSCODE_EXTENSION_ID } from '../../shared/extensions' import { Commands, VsCodeCommandArg, placeholder } from '../../shared/vscode/commands2' import * as CodeWhispererConstants from '../models/constants' import { DefaultCodeWhispererClient } from '../client/codewhisperer' -import { startSecurityScanWithProgress, confirmStopSecurityScan } from './startSecurityScan' +import { confirmStopSecurityScan, startSecurityScan } from './startSecurityScan' import { SecurityPanelViewProvider } from '../views/securityPanelViewProvider' -import { CodeScanIssue, CodeScansState, codeScanState, CodeSuggestionsState, vsCodeState } from '../models/model' +import { + codeFixState, + CodeScanIssue, + CodeScansState, + codeScanState, + CodeSuggestionsState, + onDemandFileScanState, + SecurityIssueFilters, + SecurityTreeViewFilterState, + severities, + vsCodeState, +} from '../models/model' import { connectToEnterpriseSso, getStartUrl } from '../util/getStartUrl' import { showCodeWhispererConnectionPrompt } from '../util/showSsoPrompt' import { ReferenceLogViewProvider } from '../service/referenceLogViewProvider' import { AuthUtil } from '../util/authUtil' import { isCloud9 } from '../../shared/extensionUtilities' import { getLogger } from '../../shared/logger' -import { isExtensionActive, isExtensionInstalled, openUrl } from '../../shared/utilities/vsCodeUtils' +import { isExtensionActive, isExtensionInstalled, localize, openUrl } from '../../shared/utilities/vsCodeUtils' import { getPersistedCustomizations, notifyNewCustomizations, selectCustomization, showCustomizationPrompt, } from '../util/customizationUtil' -import { applyPatch } from 'diff' -import { closeSecurityIssueWebview, showSecurityIssueWebview } from '../views/securityIssue/securityIssueWebview' +import { + closeSecurityIssueWebview, + isSecurityIssueWebviewOpen, + showSecurityIssueWebview, + updateSecurityIssueWebview, +} from '../views/securityIssue/securityIssueWebview' import { Mutable } from '../../shared/utilities/tsUtils' import { CodeWhispererSource } from './types' import { TelemetryHelper } from '../util/telemetryHelper' @@ -34,15 +49,24 @@ import { Auth, AwsConnection } from '../../auth' import { once } from '../../shared/utilities/functionUtils' import { focusAmazonQPanel } from '../../codewhispererChat/commands/registerCommands' import { removeDiagnostic } from '../service/diagnosticsProvider' -import { SecurityIssueHoverProvider } from '../service/securityIssueHoverProvider' -import { SecurityIssueCodeActionProvider } from '../service/securityIssueCodeActionProvider' import { SsoAccessTokenProvider } from '../../auth/sso/ssoAccessTokenProvider' import { ToolkitError, getTelemetryReason, getTelemetryReasonDesc } from '../../shared/errors' import { isRemoteWorkspace } from '../../shared/vscode/env' import { isBuilderIdConnection } from '../../auth/connection' import globals from '../../shared/extensionGlobals' -import { getVscodeCliPath, tryRun } from '../../shared/utilities/pathFind' +import { getVscodeCliPath } from '../../shared/utilities/pathFind' import { setContext } from '../../shared/vscode/setContext' +import { tryRun } from '../../shared/utilities/pathFind' +import { IssueItem, SecurityIssueTreeViewProvider } from '../service/securityIssueTreeViewProvider' +import { SecurityIssueProvider } from '../service/securityIssueProvider' +import { CodeWhispererSettings } from '../util/codewhispererSettings' +import { closeDiff, getPatchedCode } from '../../shared/utilities/diffUtils' +import { insertCommentAboveLine } from '../../shared/utilities/commentUtils' +import { cancel, confirm } from '../../shared' +import { startCodeFixGeneration } from './startCodeFixGeneration' +import { DefaultAmazonQAppInitContext } from '../../amazonq/apps/initContext' +import path from 'path' +import { parsePatch } from 'diff' const MessageTimeOut = 5_000 @@ -126,6 +150,20 @@ export const showReferenceLog = Commands.declare( } ) +export const showExploreAgentsView = Commands.declare( + { id: 'aws.amazonq.exploreAgents', compositeKey: { 1: 'source' } }, + () => async (_: VsCodeCommandArg, source: CodeWhispererSource) => { + if (_ !== placeholder) { + source = 'ellipsesMenu' + } + + DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().publish({ + sender: 'amazonqCore', + command: 'showExploreAgentsView', + }) + } +) + export const showIntroduction = Commands.declare('aws.amazonq.introduction', () => async () => { void openUrl(vscode.Uri.parse(CodeWhispererConstants.learnMoreUriGeneral)) }) @@ -133,18 +171,67 @@ export const showIntroduction = Commands.declare('aws.amazonq.introduction', () export const showSecurityScan = Commands.declare( { id: 'aws.amazonq.security.scan', compositeKey: { 1: 'source' } }, (context: ExtContext, securityPanelViewProvider: SecurityPanelViewProvider, client: DefaultCodeWhispererClient) => - async (_: VsCodeCommandArg, source: CodeWhispererSource) => { + async (_: VsCodeCommandArg, source: CodeWhispererSource, initiatedByChat: boolean, scanUuid?: string) => { if (AuthUtil.instance.isConnectionExpired()) { await AuthUtil.instance.notifyReauthenticate() } if (codeScanState.isNotStarted()) { // User intends to start as "Start Security Scan" is shown in the explorer tree codeScanState.setToRunning() - void startSecurityScanWithProgress(securityPanelViewProvider, client, context.extensionContext) + void startSecurityScan( + securityPanelViewProvider, + undefined, + client, + context.extensionContext, + CodeWhispererConstants.CodeAnalysisScope.PROJECT, + initiatedByChat, + undefined, + scanUuid + ) } else if (codeScanState.isRunning()) { // User intends to stop as "Stop Security Scan" is shown in the explorer tree // Cancel only when the code scan state is "Running" - await confirmStopSecurityScan() + await confirmStopSecurityScan( + codeScanState, + initiatedByChat, + CodeWhispererConstants.CodeAnalysisScope.PROJECT, + undefined + ) + } + vsCodeState.isFreeTierLimitReached = false + } +) + +export const showFileScan = Commands.declare( + { id: 'aws.amazonq.security.filescan', compositeKey: { 1: 'source' } }, + (context: ExtContext, securityPanelViewProvider: SecurityPanelViewProvider, client: DefaultCodeWhispererClient) => + async (_: VsCodeCommandArg, source: CodeWhispererSource, scanUuid?: string) => { + if (AuthUtil.instance.isConnectionExpired()) { + await AuthUtil.instance.notifyReauthenticate() + } + const editor = vscode.window.activeTextEditor + if (onDemandFileScanState.isNotStarted()) { + onDemandFileScanState.setToRunning() + void startSecurityScan( + securityPanelViewProvider, + editor, + client, + context.extensionContext, + CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND, + true, + undefined, + scanUuid + ) + } else if (onDemandFileScanState.isRunning()) { + // TODO: Pending with progress bar implementation in the Q chat Panel + // User intends to stop the scan from Q chat panel. + // Cancel only when the file scan state is "Running" + await confirmStopSecurityScan( + onDemandFileScanState, + true, + CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND, + editor?.document.fileName + ) } vsCodeState.isFreeTierLimitReached = false } @@ -262,25 +349,27 @@ export const updateReferenceLog = Commands.declare( export const openSecurityIssuePanel = Commands.declare( 'aws.amazonq.openSecurityIssuePanel', - (context: ExtContext) => async (issue: CodeScanIssue, filePath: string) => { - await showSecurityIssueWebview(context.extensionContext, issue, filePath) + (context: ExtContext) => async (issue: CodeScanIssue | IssueItem, filePath: string) => { + const targetIssue: CodeScanIssue = issue instanceof IssueItem ? issue.issue : issue + const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath + await showSecurityIssueWebview(context.extensionContext, targetIssue, targetFilePath) telemetry.codewhisperer_codeScanIssueViewDetails.emit({ - findingId: issue.findingId, - detectorId: issue.detectorId, - ruleId: issue.ruleId, + findingId: targetIssue.findingId, + detectorId: targetIssue.detectorId, + ruleId: targetIssue.ruleId, credentialStartUrl: AuthUtil.instance.startUrl, }) TelemetryHelper.instance.sendCodeScanRemediationsEvent( undefined, 'CODESCAN_ISSUE_VIEW_DETAILS', - issue.detectorId, - issue.findingId, - issue.ruleId, + targetIssue.detectorId, + targetIssue.findingId, + targetIssue.ruleId, undefined, undefined, undefined, - !!issue.suggestedFixes.length + !!targetIssue.suggestedFixes.length ) } ) @@ -342,50 +431,120 @@ export const installAmazonQExtension = Commands.declare( export const applySecurityFix = Commands.declare( 'aws.amazonq.applySecurityFix', - () => async (issue: CodeScanIssue, filePath: string, source: Component) => { - const [suggestedFix] = issue.suggestedFixes - if (!suggestedFix || !filePath) { + () => async (issue: CodeScanIssue | IssueItem, filePath: string, source: Component) => { + const targetIssue: CodeScanIssue = issue instanceof IssueItem ? issue.issue : issue + const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath + const targetSource: Component = issue instanceof IssueItem ? 'tree' : source + const [suggestedFix] = targetIssue.suggestedFixes + if (!suggestedFix || !targetFilePath || !suggestedFix.code) { return } const applyFixTelemetryEntry: Mutable = { - detectorId: issue.detectorId, - findingId: issue.findingId, - ruleId: issue.ruleId, - component: source, + detectorId: targetIssue.detectorId, + findingId: targetIssue.findingId, + ruleId: targetIssue.ruleId, + component: targetSource, result: 'Succeeded', credentialStartUrl: AuthUtil.instance.startUrl, + codeFixAction: 'applyFix', } let languageId = undefined try { - const patch = suggestedFix.code - const document = await vscode.workspace.openTextDocument(filePath) - const fileContent = document.getText() + const document = await vscode.workspace.openTextDocument(targetFilePath) languageId = document.languageId - const updatedContent = applyPatch(fileContent, patch, { fuzzFactor: 4 }) + const updatedContent = await getPatchedCode(targetFilePath, suggestedFix.code) if (!updatedContent) { void vscode.window.showErrorMessage(CodeWhispererConstants.codeFixAppliedFailedMessage) throw Error('Failed to get updated content from applying diff patch') } const edit = new vscode.WorkspaceEdit() - edit.replace( - document.uri, - new vscode.Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end), - updatedContent - ) + const diffs = parsePatch(suggestedFix.code) + for (const diff of diffs) { + for (const hunk of [...diff.hunks].reverse()) { + const startLine = document.lineAt(hunk.oldStart - 1) + const endLine = document.lineAt(hunk.oldStart - 1 + hunk.oldLines - 1) + const range = new vscode.Range(startLine.range.start, endLine.range.end) + + const newText = updatedContent + .split('\n') + .slice(hunk.newStart - 1, hunk.newStart - 1 + hunk.newLines) + .join('\n') + + edit.replace(document.uri, range, newText) + } + } const isApplied = await vscode.workspace.applyEdit(edit) - if (!isApplied) { + if (isApplied) { + void document.save().then((didSave) => { + if (!didSave) { + getLogger().error('Apply fix command failed to save the document.') + } + }) + } else { throw Error('Failed to apply edit to the workspace.') } + // add accepted references to reference log, if any + const fileName = path.basename(targetFilePath) + const time = new Date().toLocaleString() + // TODO: this is duplicated in controller.ts for test. Fix this later. + suggestedFix.references?.forEach((reference) => { + getLogger().debug('Processing reference: %O', reference) + // Log values for debugging + getLogger().debug('suggested fix code: %s', suggestedFix.code) + getLogger().debug('updated content: %s', updatedContent) + getLogger().debug( + 'start: %d, end: %d', + reference.recommendationContentSpan?.start, + reference.recommendationContentSpan?.end + ) + // given a start and end index, figure out which line number they belong to when splitting a string on /n characters + const getLineNumber = (content: string, index: number): number => { + const lines = content.slice(0, index).split('\n') + return lines.length + } + const startLine = getLineNumber(updatedContent, reference.recommendationContentSpan!.start!) + const endLine = getLineNumber(updatedContent, reference.recommendationContentSpan!.end!) + getLogger().debug('startLine: %d, endLine: %d', startLine, endLine) + const code = updatedContent.slice( + reference.recommendationContentSpan?.start, + reference.recommendationContentSpan?.end + ) + getLogger().debug('Extracted code slice: %s', code) + const referenceLog = + `[${time}] Accepted recommendation ` + + CodeWhispererConstants.referenceLogText( + `
${code}
`, + reference.licenseName!, + reference.repository!, + fileName, + startLine === endLine ? `(line at ${startLine})` : `(lines from ${startLine} to ${endLine})` + ) + + '
' + getLogger().debug('Adding reference log: %s', referenceLog) + ReferenceLogViewProvider.instance.addReferenceLog(referenceLog) + }) - if (CodeScansState.instance.isScansEnabled()) { - removeDiagnostic(document.uri, issue) - SecurityIssueHoverProvider.instance.removeIssue(document.uri, issue) - SecurityIssueCodeActionProvider.instance.removeIssue(document.uri, issue) + removeDiagnostic(document.uri, targetIssue) + SecurityIssueProvider.instance.removeIssue(document.uri, targetIssue) + SecurityIssueTreeViewProvider.instance.refresh() + + await closeSecurityIssueWebview(targetIssue.findingId) + await closeDiff(targetFilePath) + await vscode.window.showTextDocument(document, { viewColumn: vscode.ViewColumn.One }) + const linesLength = suggestedFix.code.split('\n').length + const charsLength = suggestedFix.code.length + if (targetIssue.fixJobId) { + TelemetryHelper.instance.sendCodeFixAcceptanceEvent( + targetIssue.fixJobId, + languageId, + targetIssue.ruleId, + targetIssue.detectorId, + linesLength, + charsLength + ) } - - await closeSecurityIssueWebview(issue.findingId) } catch (err) { getLogger().error(`Apply fix command failed. ${err}`) applyFixTelemetryEntry.result = 'Failed' @@ -396,13 +555,13 @@ export const applySecurityFix = Commands.declare( TelemetryHelper.instance.sendCodeScanRemediationsEvent( languageId, 'CODESCAN_ISSUE_APPLY_FIX', - issue.detectorId, - issue.findingId, - issue.ruleId, + targetIssue.detectorId, + targetIssue.findingId, + targetIssue.ruleId, source, applyFixTelemetryEntry.reasonDesc, applyFixTelemetryEntry.result, - !!issue.suggestedFixes.length + !!targetIssue.suggestedFixes.length ) } } @@ -412,6 +571,7 @@ export const signoutCodeWhisperer = Commands.declare( { id: 'aws.amazonq.signout', compositeKey: { 1: 'source' } }, (auth: AuthUtil) => async (_: VsCodeCommandArg, source: CodeWhispererSource) => { await auth.secondaryAuth.deleteConnection() + SecurityIssueTreeViewProvider.instance.refresh() return focusAmazonQPanel.execute(placeholder, source) } ) @@ -454,7 +614,7 @@ export const registerToolkitApiCallback = Commands.declare( // we need to do it manually here because the Toolkit would have been unable to call // this API if the Q/CW extension started afterwards (and this code block is running). if (isExtensionInstalled(VSCODE_EXTENSION_ID.awstoolkit)) { - getLogger().info(`Trying to register toolkit callback. Toolkit is installed, + getLogger().info(`Trying to register toolkit callback. Toolkit is installed, toolkit activated = ${isExtensionActive(VSCODE_EXTENSION_ID.awstoolkit)}`) if (toolkitApi) { // when this command is executed by AWS Toolkit activation @@ -462,7 +622,7 @@ export const registerToolkitApiCallback = Commands.declare( } else if (isExtensionActive(VSCODE_EXTENSION_ID.awstoolkit)) { // when this command is executed by Amazon Q activation const toolkitExt = vscode.extensions.getExtension(VSCODE_EXTENSION_ID.awstoolkit) - _toolkitApi = toolkitExt?.exports.getApi(VSCODE_EXTENSION_ID.amazonq) + _toolkitApi = toolkitExt?.exports?.getApi(VSCODE_EXTENSION_ID.amazonq) } if (_toolkitApi) { registerToolkitApiCallbackOnce() @@ -483,3 +643,258 @@ export const registerToolkitApiCallback = Commands.declare( } } ) + +export const clearFilters = Commands.declare( + { id: 'aws.amazonq.securityIssuesTreeFilter.clearFilters' }, + () => async () => { + await SecurityTreeViewFilterState.instance.resetFilters() + } +) + +export const generateFix = Commands.declare( + { id: 'aws.amazonq.security.generateFix' }, + (client: DefaultCodeWhispererClient, context: ExtContext) => + async ( + issue: CodeScanIssue | IssueItem | undefined, + filePath: string, + source: Component, + refresh: boolean = false + ) => { + const targetIssue: CodeScanIssue | undefined = issue instanceof IssueItem ? issue.issue : issue + const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath + const targetSource: Component = issue instanceof IssueItem ? 'tree' : source + if (!targetIssue) { + return + } + await telemetry.codewhisperer_codeScanIssueGenerateFix.run(async () => { + try { + await vscode.commands + .executeCommand('aws.amazonq.openSecurityIssuePanel', targetIssue, targetFilePath) + .then(undefined, (e) => { + getLogger().error('Failed to open security issue panel: %s', e.message) + }) + await updateSecurityIssueWebview({ + isGenerateFixLoading: true, + isGenerateFixError: false, + context: context.extensionContext, + filePath: targetFilePath, + shouldRefreshView: false, + }) + + codeFixState.setToRunning() + let hasSuggestedFix = false + const { suggestedFix, jobId } = await startCodeFixGeneration( + client, + targetIssue, + targetFilePath, + targetIssue.findingId + ) + // redact the fix if the user disabled references and there is a reference + if ( + // TODO: enable references later for scans + // !CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled() && + suggestedFix?.references && + suggestedFix?.references?.length > 0 + ) { + getLogger().debug( + `Received fix with reference and user settings disallow references. Job ID: ${jobId}` + ) + // TODO: re-enable notifications once references published + // void vscode.window.showInformationMessage( + // 'Your settings do not allow code generation with references.' + // ) + hasSuggestedFix = false + } else { + hasSuggestedFix = suggestedFix !== undefined + } + const updatedIssue: CodeScanIssue = { + ...targetIssue, + fixJobId: jobId, + suggestedFixes: + hasSuggestedFix && suggestedFix + ? [ + { + code: suggestedFix.codeDiff, + description: suggestedFix.description ?? '', + references: suggestedFix.references, + }, + ] + : [], + } + await updateSecurityIssueWebview({ + issue: updatedIssue, + isGenerateFixLoading: false, + filePath: targetFilePath, + context: context.extensionContext, + shouldRefreshView: true, + }) + + SecurityIssueProvider.instance.updateIssue(updatedIssue, targetFilePath) + SecurityIssueTreeViewProvider.instance.refresh() + } catch (err) { + await updateSecurityIssueWebview({ + issue: targetIssue, + isGenerateFixLoading: false, + isGenerateFixError: true, + filePath: targetFilePath, + context: context.extensionContext, + shouldRefreshView: true, + }) + SecurityIssueProvider.instance.updateIssue(targetIssue, targetFilePath) + SecurityIssueTreeViewProvider.instance.refresh() + throw err + } + telemetry.record({ + component: targetSource, + detectorId: targetIssue.detectorId, + findingId: targetIssue.findingId, + ruleId: targetIssue.ruleId, + variant: refresh ? 'refresh' : undefined, + }) + }) + } +) + +export const rejectFix = Commands.declare( + { id: 'aws.amazonq.security.rejectFix' }, + (context: vscode.ExtensionContext) => async (issue: CodeScanIssue | IssueItem | undefined, filePath: string) => { + const targetIssue: CodeScanIssue | undefined = issue instanceof IssueItem ? issue.issue : issue + const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath + if (!targetIssue) { + return + } + const updatedIssue: CodeScanIssue = { ...targetIssue, suggestedFixes: [] } + await updateSecurityIssueWebview({ + issue: updatedIssue, + context, + filePath: targetFilePath, + shouldRefreshView: false, + }) + + SecurityIssueProvider.instance.updateIssue(updatedIssue, targetFilePath) + SecurityIssueTreeViewProvider.instance.refresh() + await closeDiff(targetFilePath) + + return updatedIssue + } +) + +export const regenerateFix = Commands.declare( + { id: 'aws.amazonq.security.regenerateFix' }, + () => async (issue: CodeScanIssue | IssueItem | undefined, filePath: string, source: Component) => { + const targetIssue: CodeScanIssue | undefined = issue instanceof IssueItem ? issue.issue : issue + const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath + const targetSource: Component = issue instanceof IssueItem ? 'tree' : source + const updatedIssue = await rejectFix.execute(targetIssue, targetFilePath) + await generateFix.execute(updatedIssue, targetFilePath, targetSource, true) + } +) + +export const explainIssue = Commands.declare( + { id: 'aws.amazonq.security.explain' }, + () => async (issueItem: IssueItem) => { + await vscode.commands.executeCommand('aws.amazonq.explainIssue', issueItem.issue) + } +) + +export const ignoreAllIssues = Commands.declare( + { id: 'aws.amazonq.security.ignoreAll' }, + () => async (issue: CodeScanIssue | IssueItem, source: Component) => { + const targetIssue: CodeScanIssue = issue instanceof IssueItem ? issue.issue : issue + const targetSource: Component = issue instanceof IssueItem ? 'tree' : source + const resp = await vscode.window.showWarningMessage( + CodeWhispererConstants.ignoreAllIssuesMessage(targetIssue.title), + confirm, + cancel + ) + if (resp === confirm) { + await telemetry.codewhisperer_codeScanIssueIgnore.run(async () => { + const ignoredIssues = CodeWhispererSettings.instance.getIgnoredSecurityIssues() + if (!ignoredIssues.includes(targetIssue.title)) { + await CodeWhispererSettings.instance.addToIgnoredSecurityIssuesList(targetIssue.title) + } + await closeSecurityIssueWebview(targetIssue.findingId) + + telemetry.record({ + component: targetSource, + credentialStartUrl: AuthUtil.instance.startUrl, + detectorId: targetIssue.detectorId, + findingId: targetIssue.findingId, + ruleId: targetIssue.ruleId, + variant: 'all', + }) + }) + } + } +) + +export const ignoreIssue = Commands.declare( + { id: 'aws.amazonq.security.ignore' }, + () => async (issue: CodeScanIssue | IssueItem, filePath: string, source: Component) => { + await telemetry.codewhisperer_codeScanIssueIgnore.run(async () => { + const targetIssue: CodeScanIssue = issue instanceof IssueItem ? issue.issue : issue + const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath + const targetSource: Component = issue instanceof IssueItem ? 'tree' : source + const document = await vscode.workspace.openTextDocument(targetFilePath) + + const documentIsVisible = vscode.window.visibleTextEditors.some((editor) => editor.document === document) + if (!documentIsVisible) { + await vscode.window.showTextDocument(document, { + selection: new vscode.Range(targetIssue.startLine, 0, targetIssue.endLine, 0), + preserveFocus: true, + preview: true, + viewColumn: vscode.ViewColumn.One, + }) + } + insertCommentAboveLine(document, targetIssue.startLine, CodeWhispererConstants.amazonqIgnoreNextLine) + await closeSecurityIssueWebview(targetIssue.findingId) + + telemetry.record({ + component: targetSource, + credentialStartUrl: AuthUtil.instance.startUrl, + detectorId: targetIssue.detectorId, + findingId: targetIssue.findingId, + ruleId: targetIssue.ruleId, + }) + }) + } +) + +export const showSecurityIssueFilters = Commands.declare({ id: 'aws.amazonq.security.showFilters' }, () => async () => { + const filterState = SecurityTreeViewFilterState.instance.getState() + const quickPickItems: vscode.QuickPickItem[] = severities.map((severity) => ({ + label: severity, + picked: filterState.severity[severity], + })) + const result = await vscode.window.showQuickPick(quickPickItems, { + title: localize('aws.commands.amazonq.filterIssues', 'Filter Issues'), + placeHolder: localize('aws.amazonq.security.showFilters.placeholder', 'Select code issues to show'), + canPickMany: true, + }) + if (result) { + await SecurityTreeViewFilterState.instance.setState({ + ...filterState, + severity: severities.reduce( + (p, c) => ({ ...p, [c]: result.map(({ label }) => label).includes(c) }), + {} + ) as SecurityIssueFilters['severity'], + }) + } +}) + +export const focusIssue = Commands.declare( + { id: 'aws.amazonq.security.focusIssue' }, + () => async (issue: CodeScanIssue, filePath: string) => { + const document = await vscode.workspace.openTextDocument(filePath) + void vscode.window.showTextDocument(document, { + selection: new vscode.Range(issue.startLine, 0, issue.endLine, 0), + preserveFocus: true, + preview: true, + viewColumn: vscode.ViewColumn.One, + }) + + if (isSecurityIssueWebviewOpen()) { + void vscode.commands.executeCommand('aws.amazonq.openSecurityIssuePanel', issue, filePath) + } + } +) diff --git a/packages/core/src/codewhisperer/commands/gettingStartedPageCommands.ts b/packages/core/src/codewhisperer/commands/gettingStartedPageCommands.ts index 110a2adf011..ab7c8e00afc 100644 --- a/packages/core/src/codewhisperer/commands/gettingStartedPageCommands.ts +++ b/packages/core/src/codewhisperer/commands/gettingStartedPageCommands.ts @@ -19,8 +19,8 @@ export class CodeWhispererCommandBackend { } const prompts = AmazonQPromptSettings.instance - //To check the condition If the user has already seen the welcome message - if (!(await prompts.isPromptEnabled('codeWhispererNewWelcomeMessage'))) { + // To check the condition If the user has already seen the welcome message + if (!prompts.isPromptEnabled('codeWhispererNewWelcomeMessage')) { telemetry.ui_click.emit({ elementId: 'codewhisperer_Learn_ButtonClick', passive: true }) } return showCodeWhispererWebview(this.extContext, source) diff --git a/packages/core/src/codewhisperer/commands/startCodeFixGeneration.ts b/packages/core/src/codewhisperer/commands/startCodeFixGeneration.ts new file mode 100644 index 00000000000..32566c259e3 --- /dev/null +++ b/packages/core/src/codewhisperer/commands/startCodeFixGeneration.ts @@ -0,0 +1,116 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { fs, getLogger, tempDirPath } from '../../shared' +import { + createCodeFixJob, + getCodeFixJob, + getPresignedUrlAndUpload, + pollCodeFixJobStatus, + throwIfCancelled, +} from '../service/codeFixHandler' +import { ArtifactMap, DefaultCodeWhispererClient } from '../client/codewhisperer' +import { codeFixState, CodeScanIssue } from '../models/model' +import { CreateCodeFixError } from '../models/errors' +import AdmZip from 'adm-zip' +import path from 'path' +import { TelemetryHelper } from '../util/telemetryHelper' + +export async function startCodeFixGeneration( + client: DefaultCodeWhispererClient, + issue: CodeScanIssue, + filePath: string, + codeFixName: string +) { + /** + * Step 0: Initial code fix telemetry + */ + // TODO: Telemetry + let jobId + let linesOfFixGenerated + let charsOfFixGenerated + try { + getLogger().verbose( + `Starting code fix generation for lines ${issue.startLine + 1} through ${issue.endLine} of file ${filePath}` + ) + + /** + * Step 1: Generate zip + */ + throwIfCancelled() + const admZip = new AdmZip() + admZip.addLocalFile(filePath) + + const zipFilePath = path.join(tempDirPath, 'codefix.zip') + admZip.writeZip(zipFilePath) + + /** + * Step 2: Get presigned Url, upload and clean up + */ + let artifactMap: ArtifactMap = {} + try { + artifactMap = await getPresignedUrlAndUpload(client, zipFilePath, codeFixName) + } finally { + await fs.delete(zipFilePath) + } + + /** + * Step 3: Create code fix job + */ + throwIfCancelled() + const codeFixJob = await createCodeFixJob( + client, + artifactMap.SourceCode, + { + start: { line: issue.startLine + 1, character: 0 }, + end: { line: issue.endLine, character: 0 }, + }, + issue.recommendation.text, + codeFixName, + issue.ruleId + ) + if (codeFixJob.status === 'Failed') { + throw new CreateCodeFixError() + } + jobId = codeFixJob.jobId + issue.fixJobId = codeFixJob.jobId + getLogger().verbose(`Created code fix job.`) + + /** + * Step 4: Polling mechanism on code fix job status + */ + throwIfCancelled() + const jobStatus = await pollCodeFixJobStatus(client, String(codeFixJob.jobId)) + if (jobStatus === 'Failed') { + getLogger().verbose(`Code fix generation failed.`) + throw new CreateCodeFixError() + } + + /** + * Step 5: Process and render code fix results + */ + throwIfCancelled() + getLogger().verbose(`Code fix job succeeded and start processing result.`) + + const { suggestedFix } = await getCodeFixJob(client, String(codeFixJob.jobId)) + // eslint-disable-next-line aws-toolkits/no-json-stringify-in-log + getLogger().verbose(`Suggested fix: ${JSON.stringify(suggestedFix)}`) + return { suggestedFix, jobId } + } catch (err) { + getLogger().error('Code fix generation failed: %s', err) + throw err + } finally { + codeFixState.setToNotStarted() + if (jobId) { + TelemetryHelper.instance.sendCodeFixGenerationEvent( + jobId, + issue.language, + issue.ruleId, + issue.detectorId, + linesOfFixGenerated, + charsOfFixGenerated + ) + } + } +} diff --git a/packages/core/src/codewhisperer/commands/startSecurityScan.ts b/packages/core/src/codewhisperer/commands/startSecurityScan.ts index 8dee806f9df..698b9792187 100644 --- a/packages/core/src/codewhisperer/commands/startSecurityScan.ts +++ b/packages/core/src/codewhisperer/commands/startSecurityScan.ts @@ -24,22 +24,33 @@ import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { AggregatedCodeScanIssue, CodeScansState, + CodeScanState, codeScanState, CodeScanStoppedError, CodeScanTelemetryEntry, + onDemandFileScanState, + OnDemandFileScanState, } from '../models/model' import { cancel, ok } from '../../shared/localizedText' import { telemetry } from '../../shared/telemetry/telemetry' import { ToolkitError, getTelemetryReasonDesc, isAwsError } from '../../shared/errors' -import { openUrl } from '../../shared/utilities/vsCodeUtils' import { AuthUtil } from '../util/authUtil' import path from 'path' import { ZipMetadata, ZipUtil } from '../util/zipUtil' import { debounce } from 'lodash' import { once } from '../../shared/utilities/functionUtils' import { randomUUID } from '../../shared/crypto' -import { CodeAnalysisScope, ProjectSizeExceededErrorMessage } from '../models/constants' -import { CodeScanJobFailedError, CreateCodeScanFailedError, SecurityScanError } from '../models/errors' +import { CodeAnalysisScope, ProjectSizeExceededErrorMessage, SecurityScanStep } from '../models/constants' +import { + CodeScanJobFailedError, + CreateCodeScanFailedError, + MaximumFileScanReachedError, + MaximumProjectScanReachedError, + SecurityScanError, +} from '../models/errors' +import { SecurityIssuesTree } from '../service/securityIssueTreeViewProvider' +import { ChatSessionManager } from '../../amazonqScan/chat/storages/chatSession' +import { TelemetryHelper } from '../util/telemetryHelper' const localize = nls.loadMessageBundle() export const stopScanButton = localize('aws.codewhisperer.stopscan', 'Stop Scan') @@ -62,23 +73,23 @@ const getLogOutputChan = once(() => { export function startSecurityScanWithProgress( securityPanelViewProvider: SecurityPanelViewProvider, + editor: vscode.TextEditor | undefined, client: DefaultCodeWhispererClient, - context: vscode.ExtensionContext + context: vscode.ExtensionContext, + scope: CodeWhispererConstants.CodeAnalysisScope, + initiatedByChat: boolean ) { return vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, - title: CodeWhispererConstants.runningSecurityScan, + title: + scope === CodeWhispererConstants.CodeAnalysisScope.PROJECT + ? CodeWhispererConstants.runningSecurityScan + : CodeWhispererConstants.runningFileScan, cancellable: false, }, async () => { - await startSecurityScan( - securityPanelViewProvider, - undefined, - client, - context, - CodeWhispererConstants.CodeAnalysisScope.PROJECT - ) + await startSecurityScan(securityPanelViewProvider, editor, client, context, scope, initiatedByChat) } ) } @@ -93,14 +104,17 @@ export async function startSecurityScan( editor: vscode.TextEditor | undefined, client: DefaultCodeWhispererClient, context: vscode.ExtensionContext, - scope: CodeWhispererConstants.CodeAnalysisScope + scope: CodeWhispererConstants.CodeAnalysisScope, + initiatedByChat: boolean, + zipUtil: ZipUtil = new ZipUtil(), + scanUuid?: string ) { const logger = getLoggerForScope(scope) /** * Step 0: Initial Code Scan telemetry */ const codeScanStartTime = performance.now() - if (scope === CodeAnalysisScope.FILE) { + if (scope === CodeAnalysisScope.FILE_AUTO) { CodeScansState.instance.setLatestScanTime(codeScanStartTime) } let serviceInvocationStartTime = 0 @@ -123,14 +137,25 @@ export async function startSecurityScan( codewhispererCodeScanIssuesWithFixes: 0, credentialStartUrl: AuthUtil.instance.startUrl, codewhispererCodeScanScope: scope, + source: initiatedByChat ? 'chat' : 'menu', } + const fileName = editor?.document.fileName + const scanState = scope === CodeAnalysisScope.PROJECT ? codeScanState : onDemandFileScanState try { logger.verbose(`Starting security scan `) /** * Step 1: Generate zip */ throwIfCancelled(scope, codeScanStartTime) - const zipUtil = new ZipUtil() + if (initiatedByChat) { + scanState.getChatControllers()?.scanProgress.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + step: SecurityScanStep.GENERATE_ZIP, + scope, + fileName, + scanUuid, + }) + } const zipMetadata = await zipUtil.generateZip(editor?.document.uri, scope) const projectPaths = zipUtil.getProjectPaths() @@ -148,7 +173,17 @@ export async function startSecurityScan( /** * Step 2: Get presigned Url, upload and clean up */ + throwIfCancelled(scope, codeScanStartTime) + if (initiatedByChat) { + scanState.getChatControllers()?.scanProgress.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + step: SecurityScanStep.UPLOAD_TO_S3, + scope, + fileName, + scanUuid, + }) + } let artifactMap: ArtifactMap = {} const uploadStartTime = performance.now() const scanName = randomUUID() @@ -163,6 +198,15 @@ export async function startSecurityScan( * Step 3: Create scan job */ throwIfCancelled(scope, codeScanStartTime) + if (initiatedByChat) { + scanState.getChatControllers()?.scanProgress.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + step: SecurityScanStep.CREATE_SCAN_JOB, + scope, + fileName, + scanUuid, + }) + } serviceInvocationStartTime = performance.now() const scanJob = await createScanJob( client, @@ -183,6 +227,15 @@ export async function startSecurityScan( * Step 4: Polling mechanism on scan job status */ throwIfCancelled(scope, codeScanStartTime) + if (initiatedByChat) { + scanState.getChatControllers()?.scanProgress.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + step: SecurityScanStep.POLL_SCAN_STATUS, + scope, + fileName, + scanUuid, + }) + } const jobStatus = await pollScanJobStatus(client, scanJob.jobId, scope, codeScanStartTime) if (jobStatus === 'Failed') { logger.verbose(`Security scan failed.`) @@ -193,6 +246,15 @@ export async function startSecurityScan( * Step 5: Process and render scan results */ throwIfCancelled(scope, codeScanStartTime) + if (initiatedByChat) { + scanState.getChatControllers()?.scanProgress.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + step: SecurityScanStep.PROCESS_SCAN_RESULTS, + scope, + fileName, + scanUuid, + }) + } logger.verbose(`Security scan job succeeded and start processing result.`) const securityRecommendationCollection = await listScanResults( client, @@ -213,53 +275,114 @@ export async function startSecurityScan( codeScanTelemetryEntry.codewhispererCodeScanIssuesWithFixes = withFixes throwIfCancelled(scope, codeScanStartTime) logger.verbose(`Security scan totally found ${total} issues. ${withFixes} of them have fixes.`) - showSecurityScanResults( - securityPanelViewProvider, - securityRecommendationCollection, - editor, - context, - scope, - zipMetadata, - total + /** + * initiatedByChat is true for PROJECT and FILE_ON_DEMAND scopes, + * initiatedByChat is false for PROJECT and FILE_AUTO scopes + */ + if (initiatedByChat) { + showScanResultsInChat( + securityPanelViewProvider, + securityRecommendationCollection, + editor, + context, + scope, + zipMetadata, + total, + scanUuid + ) + } else { + showSecurityScanResults( + securityPanelViewProvider, + securityRecommendationCollection, + editor, + context, + scope, + zipMetadata, + total, + scanUuid + ) + } + TelemetryHelper.instance.sendCodeScanSucceededEvent( + codeScanTelemetryEntry.codewhispererLanguage, + scanJob.jobId, + total, + scope ) logger.verbose(`Security scan completed.`) } catch (error) { - getLogger().error('Security scan failed.', error) + getLogger().error('Security scan failed. %O', error) if (error instanceof CodeScanStoppedError) { + codeScanState.getChatControllers()?.scanCancelled.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + scanUuid, + }) codeScanTelemetryEntry.result = 'Cancelled' - } else { - errorPromptHelper(error as SecurityScanError, scope) + } else if (isAwsError(error) && error.code === 'ThrottlingException') { codeScanTelemetryEntry.result = 'Failed' - } - - if (isAwsError(error) && error.code === 'ThrottlingException') { if ( scope === CodeAnalysisScope.PROJECT && - error.message.includes(CodeWhispererConstants.projectScansThrottlingMessage) + error.message.includes(CodeWhispererConstants.scansLimitReachedErrorMessage) ) { - getLogger().error(CodeWhispererConstants.projectScansLimitReached) - void vscode.window.showErrorMessage(CodeWhispererConstants.projectScansLimitReached) + const maximumProjectScanReachedError = new MaximumProjectScanReachedError() + getLogger().error(maximumProjectScanReachedError.customerFacingMessage) + errorPromptHelper(maximumProjectScanReachedError, scope, initiatedByChat, fileName, scanUuid) // TODO: Should we set a graphical state? // We shouldn't set vsCodeState.isFreeTierLimitReached here because it will hide CW and Q chat options. - } else if ( - scope === CodeAnalysisScope.FILE && - error.message.includes(CodeWhispererConstants.fileScansThrottlingMessage) - ) { - getLogger().error(CodeWhispererConstants.fileScansLimitReached) + } else if (scope === CodeAnalysisScope.PROJECT) { + getLogger().error(error.message) + errorPromptHelper( + new SecurityScanError( + error.code, + (error as any).statusCode?.toString() ?? '', + 'Too many requests, please wait before trying again.' + ), + scope, + initiatedByChat, + fileName, + scanUuid + ) + } else { + const maximumFileScanReachedError = new MaximumFileScanReachedError() + getLogger().error(maximumFileScanReachedError.customerFacingMessage) + errorPromptHelper(maximumFileScanReachedError, scope, initiatedByChat, fileName, scanUuid) CodeScansState.instance.setMonthlyQuotaExceeded() } + } else { + codeScanTelemetryEntry.result = 'Failed' + errorPromptHelper( + new SecurityScanError( + (error as any).code ?? 'unknown error', + (error as any).statusCode?.toString() ?? '', + 'Encountered an unexpected error when processing the request, please try again' + ), + scope, + initiatedByChat, + fileName + ) } codeScanTelemetryEntry.reasonDesc = (error as ToolkitError)?.code === 'ContentLengthError' ? 'Payload size limit reached' : getTelemetryReasonDesc(error) codeScanTelemetryEntry.reason = (error as ToolkitError)?.code ?? 'DefaultError' + if (codeScanTelemetryEntry.codewhispererCodeScanJobId) { + TelemetryHelper.instance.sendCodeScanFailedEvent( + codeScanTelemetryEntry.codewhispererLanguage, + codeScanTelemetryEntry.codewhispererCodeScanJobId, + scope + ) + } } finally { - codeScanState.setToNotStarted() + const scanState = scope === CodeAnalysisScope.PROJECT ? codeScanState : onDemandFileScanState + scanState.setToNotStarted() + scanState.getChatControllers()?.scanStopped.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + scanUuid, + }) codeScanTelemetryEntry.duration = performance.now() - codeScanStartTime codeScanTelemetryEntry.codeScanServiceInvocationsDuration = performance.now() - serviceInvocationStartTime - await emitCodeScanTelemetry(codeScanTelemetryEntry, scope) + await emitCodeScanTelemetry(codeScanTelemetryEntry) } } @@ -270,39 +393,114 @@ export function showSecurityScanResults( context: vscode.ExtensionContext, scope: CodeWhispererConstants.CodeAnalysisScope, zipMetadata: ZipMetadata, - totalIssues: number + totalIssues: number, + scanUuid: string | undefined ) { if (isCloud9()) { securityPanelViewProvider.addLines(securityRecommendationCollection, editor) void vscode.commands.executeCommand('workbench.view.extension.aws-codewhisperer-security-panel') } else { initSecurityScanRender(securityRecommendationCollection, context, editor, scope) - if (scope === CodeWhispererConstants.CodeAnalysisScope.PROJECT) { - void vscode.commands.executeCommand('workbench.action.problems.focus') + if ( + totalIssues > 0 && + (scope === CodeWhispererConstants.CodeAnalysisScope.PROJECT || + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND) + ) { + SecurityIssuesTree.instance.focus() } } if (scope === CodeWhispererConstants.CodeAnalysisScope.PROJECT) { populateCodeScanLogStream(zipMetadata.scannedFiles) - showScanCompletedNotification(totalIssues, zipMetadata.scannedFiles) } } -export async function emitCodeScanTelemetry( - codeScanTelemetryEntry: CodeScanTelemetryEntry, - scope: CodeWhispererConstants.CodeAnalysisScope +export function showScanResultsInChat( + securityPanelViewProvider: SecurityPanelViewProvider, + securityRecommendationCollection: AggregatedCodeScanIssue[], + editor: vscode.TextEditor | undefined, + context: vscode.ExtensionContext, + scope: CodeWhispererConstants.CodeAnalysisScope, + zipMetadata: ZipMetadata, + totalIssues: number, + scanUuid: string | undefined ) { + if (isCloud9()) { + securityPanelViewProvider.addLines(securityRecommendationCollection, editor) + void vscode.commands.executeCommand('workbench.view.extension.aws-codewhisperer-security-panel') + } else { + const tabID = ChatSessionManager.Instance.getSession().tabID + const eventData = { + message: 'Show Findings in the Chat panel', + totalIssues, + securityRecommendationCollection, + fileName: scope === CodeAnalysisScope.FILE_ON_DEMAND ? [...zipMetadata.scannedFiles][0] : undefined, + tabID, + scope, + scanUuid, + } + switch (scope) { + case CodeAnalysisScope.PROJECT: + codeScanState.getChatControllers()?.showSecurityScan.fire(eventData) + break + case CodeAnalysisScope.FILE_ON_DEMAND: + onDemandFileScanState.getChatControllers()?.showSecurityScan.fire(eventData) + break + default: + break + } + initSecurityScanRender(securityRecommendationCollection, context, editor, scope) + if (totalIssues > 0) { + SecurityIssuesTree.instance.focus() + } + } + populateCodeScanLogStream(zipMetadata.scannedFiles) + if (scope === CodeAnalysisScope.PROJECT) { + showScanCompletedNotification(totalIssues, zipMetadata.scannedFiles) + } +} + +export async function emitCodeScanTelemetry(codeScanTelemetryEntry: CodeScanTelemetryEntry) { codeScanTelemetryEntry.codewhispererCodeScanProjectBytes = 0 telemetry.codewhisperer_securityScan.emit({ ...codeScanTelemetryEntry, - passive: codeScanTelemetryEntry.codewhispererCodeScanScope === CodeAnalysisScope.FILE, + passive: codeScanTelemetryEntry.codewhispererCodeScanScope === CodeAnalysisScope.FILE_AUTO, }) } -export function errorPromptHelper(error: SecurityScanError, scope: CodeAnalysisScope) { - if (scope === CodeAnalysisScope.PROJECT) { - const message = - error.code === 'ContentLengthError' ? ProjectSizeExceededErrorMessage : error.customerFacingMessage - void vscode.window.showWarningMessage(message, ok) +export function errorPromptHelper( + error: SecurityScanError, + scope: CodeAnalysisScope, + initiatedByChat: boolean, + fileName?: string, + scanUuid?: string +) { + if (scope === CodeAnalysisScope.FILE_AUTO) { + return + } + if (initiatedByChat) { + const state = scope === CodeAnalysisScope.PROJECT ? codeScanState : onDemandFileScanState + state.getChatControllers()?.errorThrown.fire({ + error, + tabID: ChatSessionManager.Instance.getSession().tabID, + scope, + fileName, + scanUuid, + }) + } + if (error.code !== 'NoSourceFilesError') { + void vscode.window.showWarningMessage(getErrorMessage(error), ok) + } +} + +function getErrorMessage(error: any): string { + switch (error.code) { + case 'ContentLengthError': + return ProjectSizeExceededErrorMessage + case 'MaximumProjectScanReachedError': + case 'MaximumFileScanReachedError': + return CodeWhispererConstants.monthlyLimitReachedNotification + default: + return error.customerFacingMessage } } @@ -323,28 +521,36 @@ function populateCodeScanLogStream(scannedFiles: Set) { } } -export async function confirmStopSecurityScan() { +export async function confirmStopSecurityScan( + state: CodeScanState | OnDemandFileScanState, + initiatedByChat: boolean, + scope: CodeWhispererConstants.CodeAnalysisScope, + fileName: string | undefined, + scanUuid?: string +) { // Confirm if user wants to stop security scan const resp = await vscode.window.showWarningMessage(CodeWhispererConstants.stopScanMessage, stopScanButton, cancel) - if (resp === stopScanButton && codeScanState.isRunning()) { + if (resp === stopScanButton && state.isRunning()) { getLogger().verbose('User requested to stop security scan. Stopping security scan.') - codeScanState.setToCancelling() + state.setToCancelling() + if (initiatedByChat) { + const scanState = scope === CodeAnalysisScope.PROJECT ? codeScanState : onDemandFileScanState + const scopeText = scope === CodeAnalysisScope.PROJECT ? 'Project' : 'File' + scanState.getChatControllers()?.errorThrown.fire({ + error: scopeText + CodeWhispererConstants.stopScanMessageInChat, + tabID: ChatSessionManager.Instance.getSession().tabID, + scope, + fileName, + }) + } } } function showScanCompletedNotification(total: number, scannedFiles: Set) { - const totalFiles = `${scannedFiles.size} ${scannedFiles.size === 1 ? 'file' : 'files'}` - const totalIssues = `${total} ${total === 1 ? 'issue was' : 'issues were'}` - const learnMore = 'Learn More' const items = [CodeWhispererConstants.showScannedFilesMessage] - void vscode.window - .showInformationMessage(`Security scan completed for ${totalFiles}. ${totalIssues} found.`, ...items) - .then((value) => { - if (value === CodeWhispererConstants.showScannedFilesMessage) { - const [, codeScanOutpuChan] = getLogOutputChan() - codeScanOutpuChan.show() - } else if (value === learnMore) { - void openUrl(vscode.Uri.parse(CodeWhispererConstants.securityScanLearnMoreUri)) - } - }) + void vscode.window.showInformationMessage(`Code Review Completed`, ...items).then((value) => { + if (total > 0 && value === CodeWhispererConstants.showScannedFilesMessage) { + SecurityIssuesTree.instance.focus() + } + }) } diff --git a/packages/core/src/codewhisperer/commands/startTestGeneration.ts b/packages/core/src/codewhisperer/commands/startTestGeneration.ts new file mode 100644 index 00000000000..25264370c37 --- /dev/null +++ b/packages/core/src/codewhisperer/commands/startTestGeneration.ts @@ -0,0 +1,260 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getLogger } from '../../shared/logger' +import { ZipUtil } from '../util/zipUtil' +import { ArtifactMap } from '../client/codewhisperer' +import { testGenerationLogsDir } from '../../shared/filesystemUtilities' +import { + createTestJob, + exportResultsArchive, + getPresignedUrlAndUploadTestGen, + pollTestJobStatus, + throwIfCancelled, +} from '../service/testGenHandler' +import path from 'path' +import { testGenState } from '..' +import { ChatSessionManager } from '../../amazonqTest/chat/storages/chatSession' +import { ChildProcess, spawn } from 'child_process' // eslint-disable-line no-restricted-imports +import { BuildStatus } from '../../amazonqTest/chat/session/session' +import { fs } from '../../shared/fs/fs' +import { TestGenerationJobStatus } from '../models/constants' +import { TestGenFailedError } from '../models/errors' +import { Range } from '../client/codewhispereruserclient' + +// eslint-disable-next-line unicorn/no-null +let spawnResult: ChildProcess | null = null +let isCancelled = false +export async function startTestGenerationProcess( + fileName: string, + filePath: string, + userInputPrompt: string, + tabID: string, + initialExecution: boolean, + selectionRange?: Range +) { + const logger = getLogger() + const session = ChatSessionManager.Instance.getSession() + // TODO: Step 0: Initial Test Gen telemetry + try { + logger.verbose(`Starting Test Generation `) + logger.verbose(`Tab ID: ${tabID} !== ${session.tabID}`) + if (tabID !== session.tabID) { + logger.verbose(`Tab ID mismatch: ${tabID} !== ${session.tabID}`) + return + } + /** + * Zip the project + */ + + const zipUtil = new ZipUtil() + if (initialExecution) { + const projectPath = zipUtil.getProjectPath(filePath) ?? '' + const relativeTargetPath = path.relative(projectPath, filePath) + session.listOfTestGenerationJobId = [] + session.shortAnswer = undefined + session.sourceFilePath = relativeTargetPath + session.projectRootPath = projectPath + session.listOfTestGenerationJobId = [] + } + const zipMetadata = await zipUtil.generateZipTestGen(session.projectRootPath, initialExecution) + session.srcPayloadSize = zipMetadata.buildPayloadSizeInBytes + session.srcZipFileSize = zipMetadata.zipFileSizeInBytes + + /** + * Step 2: Get presigned Url, upload and clean up + */ + throwIfCancelled() + if (!shouldContinueRunning(tabID)) { + return + } + let artifactMap: ArtifactMap = {} + const uploadStartTime = performance.now() + try { + artifactMap = await getPresignedUrlAndUploadTestGen(zipMetadata) + } finally { + if (await fs.existsFile(path.join(testGenerationLogsDir, 'output.log'))) { + await fs.delete(path.join(testGenerationLogsDir, 'output.log')) + } + await zipUtil.removeTmpFiles(zipMetadata) + session.artifactsUploadDuration = performance.now() - uploadStartTime + } + + /** + * Step 3: Create scan job with startTestGeneration + */ + throwIfCancelled() + if (!shouldContinueRunning(tabID)) { + return + } + const sessionFilePath = session.sourceFilePath + const testJob = await createTestJob( + artifactMap, + [ + { + relativeTargetPath: sessionFilePath, + targetLineRangeList: selectionRange ? [selectionRange] : [], + }, + ], + userInputPrompt + ) + if (!testJob.testGenerationJob) { + throw Error('Test job not found') + } + session.testGenerationJob = testJob.testGenerationJob + + /** + * Step 4: Polling mechanism on test job status with getTestGenStatus + */ + throwIfCancelled() + if (!shouldContinueRunning(tabID)) { + return + } + const jobStatus = await pollTestJobStatus( + testJob.testGenerationJob.testGenerationJobId, + testJob.testGenerationJob.testGenerationJobGroupName, + fileName, + initialExecution + ) + // TODO: Send status to test summary + if (jobStatus === TestGenerationJobStatus.FAILED) { + logger.verbose(`Test generation failed.`) + throw new TestGenFailedError() + } + throwIfCancelled() + if (!shouldContinueRunning(tabID)) { + return + } + /** + * Step 5: Process and show the view diff by getting the results from exportResultsArchive + */ + // https://github.com/aws/aws-toolkit-vscode/blob/0164d4145e58ae036ddf3815455ea12a159d491d/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts#L314-L405 + await exportResultsArchive( + artifactMap.SourceCode, + testJob.testGenerationJob.testGenerationJobGroupName, + testJob.testGenerationJob.testGenerationJobId, + path.basename(session.projectRootPath), + session.projectRootPath, + initialExecution + ) + } catch (error) { + logger.error(`startTestGenerationProcess failed: %O`, error) + // TODO: Send error message to Chat + testGenState.getChatControllers()?.errorThrown.fire({ + tabID: session.tabID, + error: error, + }) + } finally { + testGenState.setToNotStarted() + } +} + +export function shouldContinueRunning(tabID: string): boolean { + if (tabID !== ChatSessionManager.Instance.getSession().tabID) { + getLogger().verbose(`Tab ID mismatch: ${tabID} !== ${ChatSessionManager.Instance.getSession().tabID}`) + return false + } + return true +} + +/** + * Run client side build with given build commands + */ +export async function runBuildCommand(listofBuildCommand: string[]): Promise { + for (const buildCommand of listofBuildCommand) { + try { + await fs.mkdir(testGenerationLogsDir) + const tmpFile = path.join(testGenerationLogsDir, 'output.log') + const result = await runLocalBuild(buildCommand, tmpFile) + if (result.isCancelled) { + return BuildStatus.CANCELLED + } + if (result.code !== 0) { + return BuildStatus.FAILURE + } + } catch (error) { + getLogger().error(`Build process error`) + return BuildStatus.FAILURE + } + } + return BuildStatus.SUCCESS +} + +function runLocalBuild( + buildCommand: string, + tmpFile: string +): Promise<{ code: number | null; isCancelled: boolean; message: string }> { + return new Promise(async (resolve, reject) => { + const environment = process.env + const repositoryPath = ChatSessionManager.Instance.getSession().projectRootPath + const [command, ...args] = buildCommand.split(' ') + getLogger().info(`Build process started for command: ${buildCommand}, for path: ${repositoryPath}`) + + let buildLogs = '' + + spawnResult = spawn(command, args, { + cwd: repositoryPath, + shell: true, + env: environment, + }) + + if (spawnResult.stdout) { + spawnResult.stdout.on('data', async (data) => { + const output = data.toString().trim() + getLogger().info(`BUILD OUTPUT: ${output}`) + buildLogs += output + }) + } + + if (spawnResult.stderr) { + spawnResult.stderr.on('data', async (data) => { + const output = data.toString().trim() + getLogger().warn(`BUILD ERROR: ${output}`) + buildLogs += output + }) + } + + spawnResult.on('close', async (code) => { + let message = '' + if (isCancelled) { + message = 'Build cancelled' + getLogger().info('BUILD CANCELLED') + } else if (code === 0) { + message = 'Build successful' + getLogger().info('BUILD SUCCESSFUL') + } else { + message = `Build failed with exit code ${code}` + getLogger().info(`BUILD FAILED with exit code ${code}`) + } + + try { + await fs.writeFile(tmpFile, buildLogs) + getLogger().info(`Build logs written to ${tmpFile}`) + } catch (error) { + getLogger().error(`Failed to write build logs to ${tmpFile}: ${error}`) + } + + resolve({ code, isCancelled, message }) + + // eslint-disable-next-line unicorn/no-null + spawnResult = null + isCancelled = false + }) + + spawnResult.on('error', (error) => { + reject(new Error(`Failed to start build process: ${error.message}`)) + }) + }) +} + +export function cancelBuild() { + if (spawnResult) { + isCancelled = true + spawnResult.kill() + getLogger().info('Build cancellation requested') + } else { + getLogger().info('No active build to cancel') + } +} diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 4d3c667f235..47217fa928d 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -4,8 +4,7 @@ */ import * as vscode from 'vscode' -import * as fs from 'fs' -import * as os from 'os' +import * as fs from 'fs' // eslint-disable-line no-restricted-imports import path from 'path' import { getLogger } from '../../shared/logger' import * as CodeWhispererConstants from '../models/constants' @@ -15,11 +14,11 @@ import { JDKVersion, jobPlanProgress, FolderInfo, - TransformationCandidateProject, ZipManifest, TransformByQStatus, + TransformationType, + TransformationCandidateProject, } from '../models/model' -import { convertDateToTimestamp } from '../../shared/utilities/textUtilities' import { createZipManifest, downloadAndExtractResultArchive, @@ -37,20 +36,23 @@ import { uploadPayload, zipCode, } from '../service/transformByQ/transformApiHandler' -import { getOpenProjects, validateOpenProjects } from '../service/transformByQ/transformProjectValidationHandler' +import { + getJavaProjects, + getOpenProjects, + validateOpenProjects, +} from '../service/transformByQ/transformProjectValidationHandler' import { getVersionData, prepareProjectDependencies, runMavenDependencyUpdateCommands, } from '../service/transformByQ/transformMavenHandler' -import { CodeTransformCancelSrcComponents, telemetry } from '../../shared/telemetry/telemetry' +import { telemetry } from '../../shared/telemetry/telemetry' import { CodeTransformTelemetryState } from '../../amazonqGumby/telemetry/codeTransformTelemetryState' -import { CancelActionPositions, calculateTotalLatency } from '../../amazonqGumby/telemetry/codeTransformTelemetry' +import { calculateTotalLatency } from '../../amazonqGumby/telemetry/codeTransformTelemetry' import { MetadataResult } from '../../shared/telemetry/telemetryClient' import { submitFeedback } from '../../feedback/vue/submitFeedback' import { placeholder } from '../../shared/vscode/commands2' import { - AbsolutePathDetectedError, AlternateDependencyVersionsNotFoundError, JavaHomeNotSetError, JobStartError, @@ -65,6 +67,7 @@ import { getJsonValuesFromManifestFile, highlightPomIssueInProject, parseVersionsListFromPomFile, + setMaven, writeLogs, } from '../service/transformByQ/transformFileHandler' import { sleep } from '../../shared/utilities/timeoutUtils' @@ -74,6 +77,8 @@ import { HumanInTheLoopManager } from '../service/transformByQ/humanInTheLoopMan import { setContext } from '../../shared/vscode/setContext' import { makeTemporaryToolkitFolder } from '../../shared' import globals from '../../shared/extensionGlobals' +import { convertDateToTimestamp } from '../../shared/datetime' +import { findStringInDirectory } from '../../shared/utilities/workspaceUtils' function getFeedbackCommentData() { const jobId = transformByQState.getJobId() @@ -81,31 +86,25 @@ function getFeedbackCommentData() { return s } -export async function processTransformFormInput( +export async function processLanguageUpgradeTransformFormInput( pathToProject: string, fromJDKVersion: JDKVersion, toJDKVersion: JDKVersion ) { + transformByQState.setTransformationType(TransformationType.LANGUAGE_UPGRADE) transformByQState.setProjectName(path.basename(pathToProject)) transformByQState.setProjectPath(pathToProject) transformByQState.setSourceJDKVersion(fromJDKVersion) transformByQState.setTargetJDKVersion(toJDKVersion) } -export async function setMaven() { - let mavenWrapperExecutableName = os.platform() === 'win32' ? 'mvnw.cmd' : 'mvnw' - const mavenWrapperExecutablePath = path.join(transformByQState.getProjectPath(), mavenWrapperExecutableName) - if (fs.existsSync(mavenWrapperExecutablePath)) { - if (mavenWrapperExecutableName === 'mvnw') { - mavenWrapperExecutableName = './mvnw' // add the './' for non-Windows - } else if (mavenWrapperExecutableName === 'mvnw.cmd') { - mavenWrapperExecutableName = '.\\mvnw.cmd' // add the '.\' for Windows - } - transformByQState.setMavenName(mavenWrapperExecutableName) - } else { - transformByQState.setMavenName('mvn') - } - getLogger().info(`CodeTransformation: using Maven ${transformByQState.getMavenName()}`) +export async function processSQLConversionTransformFormInput(pathToProject: string, schema: string) { + transformByQState.setTransformationType(TransformationType.SQL_CONVERSION) + transformByQState.setProjectName(path.basename(pathToProject)) + transformByQState.setProjectPath(pathToProject) + transformByQState.setSchema(schema) + transformByQState.setSourceJDKVersion(JDKVersion.JDK8) // use dummy value of JDK8 so that startJob API can be called + // targetJDKVersion defaults to JDK17, the only supported version, which is fine } async function validateJavaHome(): Promise { @@ -117,6 +116,8 @@ async function validateJavaHome(): Promise { javaVersionUsedByMaven = JDKVersion.JDK8 } else if (javaVersionUsedByMaven === '11.') { javaVersionUsedByMaven = JDKVersion.JDK11 + } else if (javaVersionUsedByMaven === '17.') { + javaVersionUsedByMaven = JDKVersion.JDK17 } } if (javaVersionUsedByMaven !== transformByQState.getSourceJDKVersion()) { @@ -228,45 +229,12 @@ export async function finalizeTransformByQ(status: string) { } } -export async function parseBuildFile() { - try { - const absolutePaths = ['users/', 'system/', 'volumes/', 'c:\\', 'd:\\'] - const alias = path.basename(os.homedir()) - absolutePaths.push(alias) - const buildFilePath = path.join(transformByQState.getProjectPath(), 'pom.xml') - if (fs.existsSync(buildFilePath)) { - const buildFileContents = fs.readFileSync(buildFilePath).toString().toLowerCase() - const detectedPaths = [] - for (const absolutePath of absolutePaths) { - if (buildFileContents.includes(absolutePath)) { - detectedPaths.push(absolutePath) - } - } - if (detectedPaths.length > 0) { - const warningMessage = CodeWhispererConstants.absolutePathDetectedMessage( - detectedPaths.length, - path.basename(buildFilePath), - detectedPaths.join(', ') - ) - transformByQState.getChatControllers()?.errorThrown.fire({ - error: new AbsolutePathDetectedError(warningMessage), - tabID: ChatSessionManager.Instance.getSession().tabID, - }) - getLogger().info('CodeTransformation: absolute path potentially in build file') - return warningMessage - } - } - } catch (err: any) { - // swallow error - getLogger().error(`CodeTransformation: error scanning for absolute paths, tranformation continuing: ${err}`) - } - return undefined -} - export async function preTransformationUploadCode() { await vscode.commands.executeCommand('aws.amazonq.transformationHub.focus') - void vscode.window.showInformationMessage(CodeWhispererConstants.jobStartedNotification) + void vscode.window.showInformationMessage(CodeWhispererConstants.jobStartedNotification, { + title: CodeWhispererConstants.jobStartedTitle, + }) let uploadId = '' throwIfCancelled() @@ -278,8 +246,9 @@ export async function preTransformationUploadCode() { // if the user chose to skip unit tests, add the custom build command here transformZipManifest.customBuildCommand = transformByQState.getCustomBuildCommand() const zipCodeResult = await zipCode({ - dependenciesFolder: transformByQState.getDependencyFolderInfo()!, - modulePath: transformByQState.getProjectPath(), + // dependenciesFolder will be undefined for SQL conversions since we don't compileProject + dependenciesFolder: transformByQState.getDependencyFolderInfo(), + projectPath: transformByQState.getProjectPath(), zipManifest: transformZipManifest, }) @@ -294,6 +263,7 @@ export async function preTransformationUploadCode() { transformByQState.setPayloadFilePath(payloadFilePath) uploadId = await uploadPayload(payloadFilePath) + telemetry.record({ codeTransformJobId: uploadId }) // uploadId is re-used as jobId }) } catch (err) { const errorMessage = (err as Error).message @@ -434,12 +404,6 @@ export async function openHilPomFile() { ) } -export async function openBuildLogFile() { - const logFilePath = transformByQState.getPreBuildLogFilePath() - const doc = await vscode.workspace.openTextDocument(logFilePath) - await vscode.window.showTextDocument(doc) -} - export async function terminateHILEarly(jobID: string) { // Call resume with "REJECTED" state which will put our service // back into the normal flow and will not trigger HIL again for this step @@ -607,6 +571,10 @@ export async function pollTransformationStatusUntilPlanReady(jobId: string) { throw new PollJobError() } } + if (transformByQState.getTransformationType() === TransformationType.SQL_CONVERSION) { + // for now, no plan shown with SQL conversions. later, we may add one + return + } let plan = undefined try { plan = await getTransformationPlan(jobId) @@ -651,8 +619,12 @@ export async function finalizeTransformationJob(status: string) { if (!(status === 'COMPLETED' || status === 'PARTIALLY_COMPLETED')) { getLogger().error(`CodeTransformation: ${CodeWhispererConstants.failedToCompleteJobNotification}`) jobPlanProgress['transformCode'] = StepProgress.Failed - transformByQState.setJobFailureErrorNotification(CodeWhispererConstants.failedToCompleteJobNotification) - transformByQState.setJobFailureErrorChatMessage(CodeWhispererConstants.failedToCompleteJobChatMessage) + if (!transformByQState.getJobFailureErrorNotification()) { + transformByQState.setJobFailureErrorNotification(CodeWhispererConstants.failedToCompleteJobNotification) + } + if (!transformByQState.getJobFailureErrorChatMessage()) { + transformByQState.setJobFailureErrorChatMessage(CodeWhispererConstants.failedToCompleteJobChatMessage) + } throw new Error('Job was not successful nor partially successful') } transformByQState.setToSucceeded() @@ -663,9 +635,48 @@ export async function finalizeTransformationJob(status: string) { jobPlanProgress['transformCode'] = StepProgress.Succeeded } -export async function getValidCandidateProjects(): Promise { +export async function getValidLanguageUpgradeCandidateProjects() { const openProjects = await getOpenProjects() - return validateOpenProjects(openProjects) + const javaMavenProjects = await validateOpenProjects(openProjects) + getLogger().info(`CodeTransformation: found ${javaMavenProjects.length} projects eligible for language upgrade`) + return javaMavenProjects +} + +export async function getValidSQLConversionCandidateProjects() { + const embeddedSQLProjects: TransformationCandidateProject[] = [] + await telemetry.codeTransform_validateProject.run(async () => { + telemetry.record({ + codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), + }) + const openProjects = await getOpenProjects() + const javaProjects = await getJavaProjects(openProjects) + let resultLog = '' + for (const project of javaProjects) { + // as long as at least one of these strings is found, project contains embedded SQL statements + const searchStrings = ['oracle.jdbc.OracleDriver', 'jdbc:oracle:thin:@', 'jdbc:oracle:oci:@', 'jdbc:odbc:'] + for (const str of searchStrings) { + const spawnResult = await findStringInDirectory(str, project.path) + // just for telemetry purposes + if (spawnResult.error || spawnResult.stderr) { + resultLog += `search failed: ${JSON.stringify(spawnResult)}` + } else { + resultLog += `search succeeded: ${spawnResult.exitCode}` + } + getLogger().info(`CodeTransformation: searching for ${str} in ${project.path}, result = ${resultLog}`) + if (spawnResult.exitCode === 0) { + embeddedSQLProjects.push(project) + break + } + } + } + getLogger().info( + `CodeTransformation: found ${embeddedSQLProjects.length} projects with embedded SQL statements` + ) + telemetry.record({ + codeTransformMetadata: resultLog, + }) + }) + return embeddedSQLProjects } export async function setTransformationToRunningState() { @@ -705,39 +716,46 @@ export async function postTransformationJob() { } let chatMessage = transformByQState.getJobFailureErrorChatMessage() + const diffMessage = CodeWhispererConstants.diffMessage(transformByQState.getMultipleDiffs()) if (transformByQState.isSucceeded()) { - chatMessage = CodeWhispererConstants.jobCompletedChatMessage + chatMessage = CodeWhispererConstants.jobCompletedChatMessage(diffMessage) } else if (transformByQState.isPartiallySucceeded()) { - chatMessage = CodeWhispererConstants.jobPartiallyCompletedChatMessage + chatMessage = CodeWhispererConstants.jobPartiallyCompletedChatMessage(diffMessage) } - transformByQState - .getChatControllers() - ?.transformationFinished.fire({ message: chatMessage, tabID: ChatSessionManager.Instance.getSession().tabID }) + transformByQState.getChatControllers()?.transformationFinished.fire({ + message: chatMessage, + tabID: ChatSessionManager.Instance.getSession().tabID, + }) const durationInMs = calculateTotalLatency(CodeTransformTelemetryState.instance.getStartTime()) const resultStatusMessage = transformByQState.getStatus() - const versionInfo = await getVersionData() - const mavenVersionInfoMessage = `${versionInfo[0]} (${transformByQState.getMavenName()})` - const javaVersionInfoMessage = `${versionInfo[1]} (${transformByQState.getMavenName()})` - - // Note: IntelliJ implementation of ResultStatusMessage includes additional metadata such as jobId. - telemetry.codeTransform_totalRunTime.emit({ - buildSystemVersion: mavenVersionInfoMessage, - codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), - codeTransformResultStatusMessage: resultStatusMessage, - codeTransformRunTimeLatency: durationInMs, - codeTransformLocalJavaVersion: javaVersionInfoMessage, - result: resultStatusMessage === TransformByQStatus.Succeeded ? MetadataResult.Pass : MetadataResult.Fail, - reason: resultStatusMessage, - }) + if (transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION) { + // the below is only applicable when user is doing a Java 8/11 language upgrade + const versionInfo = await getVersionData() + const mavenVersionInfoMessage = `${versionInfo[0]} (${transformByQState.getMavenName()})` + const javaVersionInfoMessage = `${versionInfo[1]} (${transformByQState.getMavenName()})` + + telemetry.codeTransform_totalRunTime.emit({ + buildSystemVersion: mavenVersionInfoMessage, + codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), + codeTransformJobId: transformByQState.getJobId(), + codeTransformResultStatusMessage: resultStatusMessage, + codeTransformRunTimeLatency: durationInMs, + codeTransformLocalJavaVersion: javaVersionInfoMessage, + result: resultStatusMessage === TransformByQStatus.Succeeded ? MetadataResult.Pass : MetadataResult.Fail, + reason: `${resultStatusMessage}-${chatMessage}`, + }) + } if (transformByQState.isSucceeded()) { - void vscode.window.showInformationMessage(CodeWhispererConstants.jobCompletedNotification) + void vscode.window.showInformationMessage(CodeWhispererConstants.jobCompletedNotification(diffMessage), { + title: CodeWhispererConstants.transformationCompletedTitle, + }) } else if (transformByQState.isPartiallySucceeded()) { void vscode.window .showInformationMessage( - CodeWhispererConstants.jobPartiallyCompletedNotification, + CodeWhispererConstants.jobPartiallyCompletedNotification(diffMessage), CodeWhispererConstants.amazonQFeedbackText ) .then((choice) => { @@ -804,55 +822,52 @@ export async function cleanupTransformationJob() { CodeTransformTelemetryState.instance.resetCodeTransformMetaDataField() } -export async function stopTransformByQ( - jobId: string, - cancelSrc: CancelActionPositions = CancelActionPositions.BottomHubPanel -) { - if (transformByQState.isRunning()) { - getLogger().info('CodeTransformation: User requested to stop transformation. Stopping transformation.') - transformByQState.setToCancelled() - transformByQState.setPolledJobStatus('CANCELLED') - await setContext('gumby.isStopButtonAvailable', false) - try { - await stopJob(jobId) - void vscode.window - .showErrorMessage( - CodeWhispererConstants.jobCancelledNotification, - CodeWhispererConstants.amazonQFeedbackText - ) - .then((choice) => { - if (choice === CodeWhispererConstants.amazonQFeedbackText) { - void submitFeedback( - placeholder, - CodeWhispererConstants.amazonQFeedbackKey, - getFeedbackCommentData() - ) - } - }) - } catch (err) { - void vscode.window - .showErrorMessage( - CodeWhispererConstants.errorStoppingJobNotification, - CodeWhispererConstants.amazonQFeedbackText - ) - .then((choice) => { - if (choice === CodeWhispererConstants.amazonQFeedbackText) { - void submitFeedback( - placeholder, - CodeWhispererConstants.amazonQFeedbackKey, - getFeedbackCommentData() - ) - } - }) - getLogger().error(`CodeTransformation: Error stopping transformation ${err}`) - } finally { - telemetry.codeTransform_jobIsCancelledByUser.emit({ - codeTransformCancelSrcComponents: cancelSrc as CodeTransformCancelSrcComponents, - codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), - result: MetadataResult.Pass, - }) +export async function stopTransformByQ(jobId: string) { + await telemetry.codeTransform_jobIsCancelledByUser.run(async () => { + telemetry.record({ + codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), + codeTransformJobId: jobId, + }) + if (transformByQState.isRunning()) { + getLogger().info('CodeTransformation: User requested to stop transformation. Stopping transformation.') + transformByQState.setToCancelled() + transformByQState.setPolledJobStatus('CANCELLED') + await setContext('gumby.isStopButtonAvailable', false) + try { + await stopJob(jobId) + void vscode.window + .showErrorMessage( + CodeWhispererConstants.jobCancelledNotification, + CodeWhispererConstants.amazonQFeedbackText + ) + .then((choice) => { + if (choice === CodeWhispererConstants.amazonQFeedbackText) { + void submitFeedback( + placeholder, + CodeWhispererConstants.amazonQFeedbackKey, + getFeedbackCommentData() + ) + } + }) + } catch (err) { + void vscode.window + .showErrorMessage( + CodeWhispererConstants.errorStoppingJobNotification, + CodeWhispererConstants.amazonQFeedbackText + ) + .then((choice) => { + if (choice === CodeWhispererConstants.amazonQFeedbackText) { + void submitFeedback( + placeholder, + CodeWhispererConstants.amazonQFeedbackKey, + getFeedbackCommentData() + ) + } + }) + getLogger().error(`CodeTransformation: Error stopping transformation ${err}`) + } } - } + }) } async function setContextVariables() { diff --git a/packages/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts index af056f8c76d..54a1c508322 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -25,6 +25,7 @@ export type { Completion, SendTelemetryEventResponse, TelemetryEvent, + InlineChatEvent, } from './client/codewhispereruserclient.d.ts' export type { default as CodeWhispererUserClient } from './client/codewhispereruserclient.d.ts' export { SecurityPanelViewProvider } from './views/securityPanelViewProvider' @@ -44,6 +45,14 @@ export { listCodeWhispererCommands, listCodeWhispererCommandsId } from './ui/sta export { refreshStatusBar, CodeWhispererStatusBar, InlineCompletionService } from './service/inlineCompletionService' export { SecurityIssueHoverProvider } from './service/securityIssueHoverProvider' export { SecurityIssueCodeActionProvider } from './service/securityIssueCodeActionProvider' +export { + SecurityIssueTreeViewProvider, + SecurityViewTreeItem, + SecurityIssuesTree, + FileItem, + IssueItem, + SeverityItem, +} from './service/securityIssueTreeViewProvider' export { invokeRecommendation } from './commands/invokeRecommendation' export { onAcceptance } from './commands/onAcceptance' export { CodeWhispererTracker } from './tracker/codewhispererTracker' @@ -87,3 +96,8 @@ export * as supplementalContextUtil from './util/supplementalContext/supplementa export * from './service/diagnosticsProvider' export * as diagnosticsProvider from './service/diagnosticsProvider' export * from './ui/codeWhispererNodes' +export { SecurityScanError } from '../codewhisperer/models/errors' +export * as CodeWhispererConstants from '../codewhisperer/models/constants' +export { getSelectedCustomization } from './util/customizationUtil' +export { Container } from './service/serviceContainer' +export * from './util/gitUtil' diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 41347803bd1..96e4c3438f0 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -22,6 +22,35 @@ export const AWSTemplateKeyWords = ['AWSTemplateFormatVersion', 'Resources', 'AW export const AWSTemplateCaseInsensitiveKeyWords = ['cloudformation', 'cfn', 'template', 'description'] +const patchDescriptions: { [key: string]: string } = { + 'Prepare minimal upgrade to Java 17': + 'This diff patch covers the set of upgrades for Springboot, JUnit, and PowerMockito frameworks.', + 'Popular Enterprise Specifications and Application Frameworks upgrade': + 'This diff patch covers the set of upgrades for Jakarta EE 10, Hibernate 6.2, and Micronaut 3.', + 'HTTP Client Utilities, Apache Commons Utilities, and Web Frameworks': + 'This diff patch covers the set of upgrades for Apache HTTP Client 5, Apache Commons utilities (Collections, IO, Lang, Math), and Struts 6.0.', + 'Testing Tools and Frameworks upgrade': + 'This diff patch covers the set of upgrades for ArchUnit, Mockito, TestContainers, and Cucumber, in addition to the Jenkins plugins and the Maven Wrapper.', + 'Miscellaneous Processing Documentation upgrade': + 'This diff patch covers a diverse set of upgrades spanning ORMs, XML processing, API documentation, and more.', + 'Deprecated API replacement, dependency upgrades, and formatting': + 'This diff patch replaces deprecated APIs, makes additional dependency version upgrades, and formats code changes.', +} + +export const JsonConfigFileNamingConvention = new Set([ + 'app.json', + 'appsettings.json', + 'bower.json', + 'composer.json', + 'db.json', + 'manifest.json', + 'package.json', + 'schema.json', + 'settings.json', + 'tsconfig.json', + 'vcpkg.json', +]) + export const normalTextChangeRegex = /[A-Za-z0-9]/g export const autoSuggestionConfig = { @@ -53,7 +82,7 @@ export const lineBreak = '\n' export const lineBreakWin = '\r\n' -export const supplementalContextTimeoutInMs = 50 +export const supplementalContextTimeoutInMs = 100 /** * Ux of recommendations @@ -98,6 +127,14 @@ export const platformLanguageIds = [ 'packer', 'plaintext', 'jsonc', + 'systemverilog', + 'verilog', + 'powershell', + 'dart', + 'lua', + 'r', + 'swift', + 'vue', ] as const export type PlatformLanguageId = (typeof platformLanguageIds)[number] @@ -107,11 +144,13 @@ export type PlatformLanguageId = (typeof platformLanguageIds)[number] */ export const pendingResponse = 'Waiting for Amazon Q...' -export const runningSecurityScan = 'Scanning project for security issues...' +export const runningSecurityScan = 'Reviewing project for code issues...' + +export const runningFileScan = 'Reviewing current file for code issues...' export const noSuggestions = 'No suggestions from Amazon Q' -export const licenseFilter = 'Amazon Q suggestions were filtered due to reference setting' +export const licenseFilter = 'Amazon Q suggestions were filtered due to reference settings' /** * Key bindings JSON file path @@ -212,22 +251,40 @@ export const projectScanUploadIntent = 'FULL_PROJECT_SECURITY_SCAN' export const codeScanTruncDirPrefix = 'codewhisperer_scan' +export const TestGenerationTruncDirPrefix = 'Q_TestGeneration' + export const codeScanZipExt = '.zip' export const contextTruncationTimeoutSeconds = 10 -export const codeScanJobTimeoutSeconds = 60 * 10 //10 minutes +export const codeScanJobTimeoutSeconds = 60 * 10 // 10 minutes + +export const codeFileScanJobTimeoutSeconds = 60 * 10 // 10 minutes -export const codeFileScanJobTimeoutSeconds = 60 //1 minute +export const codeFixJobTimeoutMs = 60_000 export const projectSizeCalculateTimeoutSeconds = 10 export const codeScanJobPollingIntervalSeconds = 1 +export const codeFixJobPollingIntervalMs = 1000 + export const fileScanPollingDelaySeconds = 10 export const projectScanPollingDelaySeconds = 30 +export const codeFixJobPollingDelayMs = 5_000 + +export const testGenPollingDelaySeconds = 10 + +export const testGenJobPollingIntervalMilliseconds = 1000 + +export const testGenJobTimeoutMilliseconds = 60 * 10 * 1000 // 10 minutes + +export const testGenUploadIntent = 'UNIT_TESTS_GENERATION' + +export const codeFixUploadIntent = 'CODE_FIX_GENERATION' + export const artifactTypeSource = 'SourceCode' export const codeScanFindingsSchema = 'codescan/findings/1.0' @@ -241,7 +298,9 @@ export const securityScanLanguageIds = [ 'java', 'python', 'javascript', + 'javascriptreact', 'typescript', + 'typescriptreact', 'csharp', 'go', 'ruby', @@ -264,6 +323,11 @@ export const securityScanLanguageIds = [ 'java-properties', 'go.mod', 'go.sum', + 'kotlin', + 'scala', + 'sh', + 'shell', + 'shellscript', ] as const export type SecurityScanLanguageId = (typeof securityScanLanguageIds)[number] @@ -284,30 +348,41 @@ export const settingsLearnMore = 'Learn More about Amazon Q Settings' export const freeTierLimitReached = 'You have reached the monthly fair use limit of code recommendations.' -export const freeTierLimitReachedCodeScan = 'You have reached the monthly quota of code scans.' +export const freeTierLimitReachedCodeScan = 'You have reached the monthly quota of code reviews.' -export const fileScansLimitReached = 'Amazon Q: You have reached the monthly limit for auto-scans.' +export const scansLimitReachedErrorMessage = + 'Maximum com.amazon.aws.codewhisperer.StartCodeAnalysis reached for this month.' -export const projectScansLimitReached = 'Amazon Q: You have reached the monthly limit for project scans.' +export const utgLimitReached = + 'Maximum com.amazon.aws.codewhisperer.runtime.StartTestGeneration reached for this month.' export const DefaultCodeScanErrorMessage = - 'Amazon Q encountered an error while scanning for security issues. Try again later.' + 'Amazon Q encountered an error while reviewing for code issues. Try again later.' + +export const defaultTestGenErrorMessage = 'Amazon Q encountered an error while generating tests. Try again later.' -export const FileSizeExceededErrorMessage = `Amazon Q: The selected file exceeds the input artifact limit. Try again with a smaller file. For more information about scan limits, see the [Amazon Q documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security-scans.html#quotas).` +export const defaultCodeFixErrorMessage = 'Amazon Q encountered an error while generating code fixes. Try again later.' -export const ProjectSizeExceededErrorMessage = `Amazon Q: The selected project exceeds the input artifact limit. Try again with a smaller project. For more information about scan limits, see the [Amazon Q documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security-scans.html#quotas).` +export const FileSizeExceededErrorMessage = `Amazon Q: The selected file exceeds the input artifact limit. Try again with a smaller file. For more information about review limits, see the [Amazon Q documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security-scans.html#quotas).` -export const noSourceFilesErrorMessage = 'Amazon Q: Project does not contain valid files to scan' +export const ProjectSizeExceededErrorMessage = `Amazon Q: The selected workspace exceeds the input artifact limit. Try again with a smaller workspace. For more information about review limits, see the [Amazon Q documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security-scans.html#quotas).` -export const UploadArtifactToS3ErrorMessage = `Amazon Q is unable to upload your workspace artifacts to Amazon S3 for security scans. For more information, see the [Amazon Q documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security_iam_manage-access-with-policies.html#data-perimeters).` +export const monthlyLimitReachedNotification = + "You've reached the monthly quota for Amazon Q Developer's agent capabilities. You can try again next month. For more information on usage limits, see the Amazon Q Developer pricing page." + +export const noSourceFilesErrorMessage = 'Amazon Q: workspace does not contain valid files to review' + +export const noActiveFileErrorMessage = 'Amazon Q: Open valid file to run a file review' + +export const UploadArtifactToS3ErrorMessage = `Amazon Q is unable to upload your workspace artifacts to Amazon S3 for security reviews. For more information, see the [Amazon Q documentation](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/security_iam_manage-access-with-policies.html#data-perimeters).` export const throttlingLearnMore = `Learn More` export const throttlingMessage = `Maximum recommendation count reached for this month` -export const fileScansThrottlingMessage = `Maximum auto-scans count reached for this month` +export const fileScansThrottlingMessage = `Maximum file reviews count reached for this month` -export const projectScansThrottlingMessage = `Maximum project scan count reached for this month` +export const projectScansThrottlingMessage = `Maximum workspace review count reached for this month` export const connectionChangeMessage = `Keep using Amazon Q with ` @@ -319,23 +394,29 @@ export const failedToConnectAwsBuilderId = `Failed to connect to AWS Builder ID` export const failedToConnectIamIdentityCenter = `Failed to connect to IAM Identity Center` export const stopScanMessage = - 'Stop security scan? This scan will be counted as one complete scan towards your monthly security scan limits.' + 'Stop security review? This review will be counted as one complete review towards your monthly security review limits.' + +// TODO: Change the Text according to the UX +export const stopScanMessageInChat = 'Review is stopped. Retry reviews by selecting below options' + +export const showScannedFilesMessage = 'View Code Issues' -export const showScannedFilesMessage = 'Show Scanned Files' +export const ignoreAllIssuesMessage = (issueTitle: string) => { + return `Are you sure you want to ignore all "${issueTitle}" issues? Amazon Q will not show these issues for future reviews. You can manage a list of your ignored issues in the Amazon Q extension settings.` +} export const updateInlineLockKey = 'CODEWHISPERER_INLINE_UPDATE_LOCK_KEY' export const newCustomizationMessage = 'You have access to new Amazon Q customizations.' // Start of QCT Strings + export const uploadZipSizeLimitInBytes = 2000000000 // 2GB export const maxBufferSize = 1024 * 1024 * 8 // this is 8MB; the default max buffer size for stdout for spawnSync is 1MB export const transformationJobPollingIntervalSeconds = 5 -export const transformationJobTimeoutSeconds = 3 * 60 * 60 // 3 hours, to match backend - export const defaultLanguage = 'Java' export const contentChecksumType = 'SHA_256' @@ -420,20 +501,40 @@ export const codeTransformLocThreshold = 100000 export const jobStartedChatMessage = 'I am starting to transform your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your project. To monitor progress, go to the Transformation Hub. If I run into any issues, I might pause the transformation to get input from you on how to proceed.' -export const uploadingCodeStepMessage = 'Uploading your code' +export const chooseTransformationObjective = `I can help you with the following tasks:\n- Upgrade your Java 8 and Java 11 codebases to Java 17, or upgrade Java 17 code with up to date libraries and other dependencies.\n- Convert embedded SQL code for Oracle to PostgreSQL database migrations in AWS DMS.\n\nWhat would you like to do? You can enter "language upgrade" or "sql conversion".` + +export const chooseTransformationObjectivePlaceholder = 'Enter "language upgrade" or "sql conversion"' + +export const userPatchDescriptionChatMessage = ` +If you'd like to update and test your code with fewer changes at a time, I can divide the transformation results into separate diff patches. If applicable to your application, I can split up the diffs up into the following groups of upgrades. Here are the upgrades included in each diff: + +• Minimal Compatible Library Upgrade to Java 17: Dependencies to the minimum compatible versions in Java 17, including Springboot, JUnit, and PowerMockito. + +• Popular Enterprise Specifications Application Frameworks: Popular enterprise and application frameworks like Jakarta EE, Hibernate, and Micronaut 3. + +• HTTP Client Utilities Web Frameworks: HTTP client libraries, Apache Commons utilities, and Struts frameworks. + +• Testing Tools Frameworks: Testing tools like ArchUnit, Mockito, and TestContainers and build tools like Jenkins and Maven Wrapper. + +• Miscellaneous Processing Documentation: Upgrades ORMs, XML processing, and Swagger to SpringDoc/OpenAPI. + +• Deprecated API replacement, dependency upgrades, and formatting: Replaces deprecated APIs, makes additional dependency version upgrades, and formats code changes. +` + +export const uploadingCodeStepMessage = 'Upload your code' export const buildCodeStepMessage = 'Build uploaded code in secure build environment' export const generatePlanStepMessage = 'Generate transformation plan' -export const transformStepMessage = 'Transform your code to Java 17 using transformation plan' +export const transformStepMessage = 'Transform your code' export const filesUploadedMessage = 'Files have been uploaded to Amazon Q, transformation job has been accepted and is preparing to start.' export const planningMessage = 'Amazon Q is analyzing your code in order to generate a transformation plan.' -export const transformingMessage = 'Amazon Q is transforming your code. Details will appear soon.' +export const transformingMessage = 'Amazon Q is transforming your code.' export const stoppingJobMessage = 'Stopping the transformation...' @@ -441,16 +542,20 @@ export const buildingCodeMessage = 'Amazon Q is building your code using Java JAVA_VERSION_HERE in a secure build environment.' export const scanningProjectMessage = - 'Amazon Q is scanning the project files and getting ready to start the job. To start the job, Amazon Q needs to upload the project artifacts. Once that is done, Amazon Q can start the transformation job. The estimated time for this operation ranges from a few seconds to several minutes.' + 'Amazon Q is reviewing the project files and getting ready to start the job. To start the job, Amazon Q needs to upload the project artifacts. Once that is done, Amazon Q can start the transformation job. The estimated time for this operation ranges from a few seconds to several minutes.' export const failedStepMessage = 'The step failed, fetching additional details...' export const jobCompletedMessage = 'The transformation completed.' +export const noChangesMadeMessage = "I didn't make any changes for this transformation." + export const noOngoingJobMessage = 'No ongoing job.' export const nothingToShowMessage = 'Nothing to show' +export const jobStartedTitle = 'Transformation started' + export const jobStartedNotification = 'Amazon Q is transforming your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your project. To monitor progress, go to the Transformation Hub.' @@ -460,8 +565,7 @@ export const startTransformationButtonText = 'Start a new transformation' export const stopTransformationButtonText = 'Stop transformation' -export const checkingForProjectsChatMessage = - 'I am checking for open projects that are eligible for Code Transformation.' +export const checkingForProjectsChatMessage = 'Checking for eligible projects...' export const buildStartedChatMessage = 'I am building your project. This can take up to 10 minutes, depending on the size of your project.' @@ -474,7 +578,23 @@ export const buildSucceededNotification = export const absolutePathDetectedMessage = (numPaths: number, buildFile: string, listOfPaths: string) => `I detected ${numPaths} potential absolute file path(s) in your ${buildFile} file: **${listOfPaths}**. Absolute file paths might cause issues when I build your code. Any errors will show up in the build log.` -export const unsupportedJavaVersionChatMessage = `Sorry, currently I can only upgrade Java 8 or Java 11 projects. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` +export const selectSQLMetadataFileHelpMessage = + 'Okay, I can convert the embedded SQL code for your Oracle to PostgreSQL transformation. To get started, upload the zipped metadata file from your schema conversion in AWS Data Migration Service (DMS). To retrieve the metadata file:\n1. Open your database migration project in the AWS DMS console.\n2. Open the schema conversion and choose **Convert the embedded SQL in your application**.\n3. Once you complete the conversion, close the project and go to the S3 bucket where your project is stored.\n4. Open the folder and find the project folder ("sct-project").\n5. Download the object inside the project folder. This will be a zip file.\n\nFor more info, refer to the [documentation](https://docs.aws.amazon.com/dms/latest/userguide/schema-conversion-save-apply.html#schema-conversion-save).' + +export const invalidMetadataFileUnsupportedSourceDB = + 'I can only convert SQL for migrations from an Oracle source database. The provided .sct file indicates another source database for this migration.' + +export const invalidMetadataFileUnsupportedTargetDB = + 'I can only convert SQL for migrations to Aurora PostgreSQL or Amazon RDS for PostgreSQL target databases. The provided .sct file indicates another target database for this migration.' + +export const invalidMetadataFileErrorParsing = + "It looks like the .sct file you provided isn't valid. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS." + +export const invalidMetadataFileNoSctFile = + "An .sct file is required for transformation. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS." + +export const sqlMetadataFileReceived = + 'I found the following source database, target database, and host based on the schema conversion metadata you provided:' export const failedToStartJobChatMessage = "Sorry, I couldn't begin the transformation. Please try starting the transformation again." @@ -488,18 +608,6 @@ export const failedToStartJobTooManyJobsChatMessage = export const failedToStartJobTooManyJobsNotification = 'Amazon Q could not begin the transformation. You have too many active transformations running. Please try again after your other transformations have completed.' -export const failedToStartJobMonthlyLimitNotification = - 'Amazon Q cannot transform your project because it will exceed the free tier limit of 2000 lines of code per month. Try transforming a smaller project.' - -export const failedToStartJobMonthlyLimitChatMessage = - 'I am sorry, I cannot transform your project because it will exceed the free tier limit of 2000 lines of code per month. You can try again with a smaller project.' - -export const failedToStartJobLinesLimitNotification = - 'Your project exceeds the free tier limit of 1000 lines of code per transformation. Try transforming a smaller project.' - -export const failedToStartJobLinesLimitChatMessage = - 'I am sorry, your project exceeds the free tier limit of 1000 lines of code per transformation. You can try again with a smaller project.' - export const failedToUploadProjectChatMessage = "Sorry, I couldn't upload your project. Please try starting the transformation again." @@ -530,28 +638,38 @@ export const jobCancelledChatMessage = export const jobCancelledNotification = 'You cancelled the transformation.' -export const jobCompletedChatMessage = - 'I upgraded your code to Java 17. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated.' +export const transformationCompletedTitle = 'Transformation complete' + +export const diffMessage = (multipleDiffs: boolean) => { + return multipleDiffs + ? 'You can review the diffs to see my proposed changes and accept or reject them. You will be able to accept changes from one diff at a time. If you reject changes in one diff, you will not be able to view or accept changes in the other diffs.' + : 'You can review the diff to see my proposed changes and accept or reject them.' +} -export const jobCompletedNotification = - 'Amazon Q upgraded your code to Java 17. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated.' +export const jobCompletedChatMessage = (multipleDiffsString: string) => { + return `I completed your transformation. ${multipleDiffsString} The transformation summary has details about the changes I'm proposing.` +} -export const jobPartiallyCompletedChatMessage = - 'I upgraded part of your code to Java 17. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation.' +export const jobCompletedNotification = (multipleDiffsString: string) => { + return `Amazon Q transformed your code. ${multipleDiffsString} The transformation summary has details about the changes.` +} -export const jobPartiallyCompletedNotification = - 'Amazon Q upgraded part of your code to Java 17. You can review the diff to see my proposed changes and accept or reject them. The transformation summary has details about the files I updated and the errors that prevented a complete transformation.' +export const jobPartiallyCompletedChatMessage = (multipleDiffsString: string) => { + return `I transformed part of your code. ${multipleDiffsString} The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` +} -export const noPomXmlFoundChatMessage = `Sorry, I couldn\'t find a project that I can upgrade. I couldn\'t find a pom.xml file in any of your open projects. Currently, I can only upgrade Java 8 or Java 11 projects built on Maven. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` +export const jobPartiallyCompletedNotification = (multipleDiffsString: string) => { + return `Amazon Q transformed part of your code. ${multipleDiffsString} The transformation summary has details about the files I updated and the errors that prevented a complete transformation.` +} -export const noPomXmlFoundNotification = `None of your open projects are supported by Amazon Q Code Transformation. Amazon Q could not find a pom.xml file in any of your open projects. Currently, Amazon Q can only upgrade Java 8 or Java 11 projects built on Maven. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` +export const noPomXmlFoundChatMessage = `I couldn\'t find a project that I can upgrade. I couldn\'t find a pom.xml file in any of your open projects, nor could I find any embedded SQL statements. Currently, I can upgrade Java 8, 11, or 17 projects built on Maven, or Oracle SQL to PostgreSQL statements in Java projects. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` export const noJavaHomeFoundChatMessage = `Sorry, I couldn\'t locate your Java installation. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` export const dependencyVersionsErrorMessage = 'I could not find any other versions of this dependency in your local Maven repository. Try transforming the dependency to make it compatible with Java 17, and then try transforming this module again.' -export const errorUploadingWithExpiredUrl = `The upload error may have been caused by the expiration of the S3 pre-signed URL that was used to upload code artifacts to Q Code Transformation. The S3 pre-signed URL expires in 30 minutes. This could be caused by any delays introduced by intermediate services in your network infrastructure. Please investigate your network configuration and consider allowlisting 'amazonq-code-transformation-us-east-1-c6160f047e0.s3.amazonaws.com' to skip any scanning that might delay the upload. For more information, see the [Amazon Q documentation](${codeTransformTroubleshootAllowS3Access}).` +export const errorUploadingWithExpiredUrl = `The upload error may have been caused by the expiration of the S3 pre-signed URL that was used to upload code artifacts to Q Code Transformation. The S3 pre-signed URL expires in 30 minutes. This could be caused by any delays introduced by intermediate services in your network infrastructure. Please investigate your network configuration and consider allowlisting 'amazonq-code-transformation-us-east-1-c6160f047e0.s3.amazonaws.com' to skip any reviewing that might delay the upload. For more information, see the [Amazon Q documentation](${codeTransformTroubleshootAllowS3Access}).` export const socketConnectionFailed = 'Please check your network connectivity or firewall configuration, and then try again.' @@ -578,13 +696,38 @@ export const viewProposedChangesChatMessage = export const viewProposedChangesNotification = 'Download complete. You can view a summary of the transformation and accept or reject the proposed changes in the Transformation Hub.' -export const changesAppliedChatMessage = 'I applied the changes to your project.' +export const changesAppliedChatMessageOneDiff = 'I applied the changes to your project.' + +export const changesAppliedChatMessageMultipleDiffs = ( + currentPatchIndex: number, + totalPatchFiles: number, + description: string | undefined +) => + description + ? `I applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project. ${patchDescriptions[description]}` + : 'I applied the changes to your project.' + +export const changesAppliedNotificationOneDiff = 'Amazon Q applied the changes to your project' + +export const changesAppliedNotificationMultipleDiffs = (currentPatchIndex: number, totalPatchFiles: number) => { + if (totalPatchFiles === 1) { + return 'Amazon Q applied the changes to your project.' + } else { + return `Amazon Q applied the changes in diff patch ${currentPatchIndex + 1} of ${totalPatchFiles} to your project.` + } +} + +export const noOpenProjectsFoundChatMessage = `I couldn\'t find a project that I can upgrade. Currently, I support Java 8, Java 11, and Java 17 projects built on Maven. Make sure your project is open in the IDE. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` + +export const noOpenFileFoundChatMessage = `Sorry, there isn't a source file open right now that I can generate a test for. Make sure you open a source file so I can generate tests.` + +export const invalidFileTypeChatMessage = `Sorry, your current active window is not a source code file. Make sure you select a source file as your primary context.` -export const changesAppliedNotification = 'Amazon Q applied the changes to your project.' +export const noOpenProjectsFoundChatTestGenMessage = `Sorry, I couldn\'t find a project to generate tests` -export const noOpenProjectsFoundChatMessage = `Sorry, I couldn\'t find a project that I can upgrade. Currently, I can only upgrade Java 8 or Java 11 projects built on Maven. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` +export const unitTestGenerationCancelMessage = 'Unit test generation cancelled.' -export const noJavaProjectsFoundChatMessage = `Sorry, I couldn\'t find a project that I can upgrade. Currently, I can only upgrade Java 8 or Java 11 projects built on Maven. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` +export const noJavaProjectsFoundChatMessage = `I couldn\'t find a project that I can upgrade. Currently, I support Java 8, Java 11, and Java 17 projects built on Maven. Make sure your project is open in the IDE. For more information, see the [Amazon Q documentation](${codeTransformPrereqDoc}).` export const linkToDocsHome = 'https://docs.aws.amazon.com/amazonq/latest/aws-builder-use-ug/code-transformation.html' @@ -604,16 +747,13 @@ export const cleanInstallErrorNotification = `Amazon Q could not run the Maven c export const enterJavaHomeChatMessage = 'Enter the path to JDK ' export const projectPromptChatMessage = - 'I can upgrade your JAVA_VERSION_HERE. To start the transformation, I need some information from you. Choose the project you want to upgrade and the target code version to upgrade to. Then, choose Transform.' + 'I can upgrade your Java project. To start the transformation, I need some information from you. Choose the project you want to upgrade and the target code version to upgrade to. Then, choose Confirm.' export const windowsJavaHomeHelpChatMessage = 'To find the JDK path, run the following commands in a new terminal: `cd "C:/Program Files/Java"` and then `dir`. If you see your JDK version, run `cd ` and then `cd` to show the path.' -export const macJava8HomeHelpChatMessage = - 'To find the JDK path, run the following command in a new terminal: `/usr/libexec/java_home -v 1.8`' - -export const macJava11HomeHelpChatMessage = - 'To find the JDK path, run the following command in a new terminal: `/usr/libexec/java_home -v 11`' +export const macJavaVersionHomeHelpChatMessage = (version: number) => + `To find the JDK path, run the following command in a new terminal: \`/usr/libexec/java_home -v ${version}\`` export const linuxJavaHomeHelpChatMessage = 'To find the JDK path, run the following command in a new terminal: `update-java-alternatives --list`' @@ -632,17 +772,27 @@ export const chooseSourceVersionFormTitle = 'Choose the source code version' export const chooseTargetVersionFormTitle = 'Choose the target code version' +export const chooseSchemaFormTitle = 'Choose the schema of the database' + +export const chooseProjectSchemaFormMessage = 'To continue, choose the project and schema for this transformation.' + export const skipUnitTestsFormTitle = 'Choose to skip unit tests' +export const selectiveTransformationFormTitle = 'Choose how to receive proposed changes' + export const skipUnitTestsFormMessage = - 'I will build your project using `mvn test` by default. If you would like me to build your project without running unit tests, I will use `mvn test-compile`.' + 'I will build your project using `mvn clean test` by default. If you would like me to build your project without running unit tests, I will use `mvn clean test-compile`.' export const runUnitTestsMessage = 'Run unit tests' +export const oneDiffMessage = 'One diff' + export const doNotSkipUnitTestsBuildCommand = 'clean test' export const skipUnitTestsMessage = 'Skip unit tests' +export const multipleDiffsMessage = 'Multiple diffs' + export const skipUnitTestsBuildCommand = 'clean test-compile' export const planTitle = 'Code Transformation plan by Amazon Q' @@ -684,12 +834,21 @@ export const supplemetalContextFetchingTimeoutMsg = 'Amazon Q supplemental conte export const codeFixAppliedFailedMessage = 'Failed to apply suggested code fix.' -export const runSecurityScanButtonTitle = 'Run security scan' +export const runSecurityScanButtonTitle = 'Run security review' + +export const startProjectScan = 'Review Project' + +export const startFileScan = 'Review Current File in Focus' + +export const noOpenProjectsFound = `Sorry, I couldn\'t find a project in the workspace. Open a project in your IDE and retry the review.` + +export const noOpenFileFound = `Sorry, I couldn\'t find an active file in the editor. Open a file in your IDE and retry the review.` export const crossFileContextConfig = { numberOfChunkToFetch: 60, topK: 3, - numberOfLinesEachChunk: 10, + numberOfLinesEachChunk: 50, + maximumTotalLength: 20480, } export const utgConfig = { @@ -697,6 +856,39 @@ export const utgConfig = { } export enum CodeAnalysisScope { - FILE = 'FILE', + FILE_AUTO = 'FILE_AUTO', + FILE_ON_DEMAND = 'FILE_ON_DEMAND', PROJECT = 'PROJECT', } + +export enum TestGenerationJobStatus { + IN_PROGRESS = 'IN_PROGRESS', + FAILED = 'FAILED', + COMPLETED = 'COMPLETED', +} + +export enum ZipUseCase { + TEST_GENERATION = 'TEST_GENERATION', + CODE_SCAN = 'CODE_SCAN', +} + +export const amazonqIgnoreNextLine = 'amazonq-ignore-next-line' + +export enum TestGenerationBuildStep { + START_STEP, + INSTALL_DEPENDENCIES, + RUN_BUILD, + RUN_EXECUTION_TESTS, + FIXING_TEST_CASES, + PROCESS_TEST_RESULTS, +} + +export enum SecurityScanStep { + GENERATE_ZIP, + UPLOAD_TO_S3, + CREATE_SCAN_JOB, + POLL_SCAN_STATUS, + PROCESS_SCAN_RESULTS, +} + +export const amazonqCodeIssueDetailsTabTitle = 'Code Issue Details' diff --git a/packages/core/src/codewhisperer/models/errors.ts b/packages/core/src/codewhisperer/models/errors.ts index 9deb401cd6c..3fe22f22af0 100644 --- a/packages/core/src/codewhisperer/models/errors.ts +++ b/packages/core/src/codewhisperer/models/errors.ts @@ -2,12 +2,16 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ +import { i18n } from '../../shared' import { ToolkitError } from '../../shared/errors' import { DefaultCodeScanErrorMessage, FileSizeExceededErrorMessage, ProjectSizeExceededErrorMessage, UploadArtifactToS3ErrorMessage, + defaultCodeFixErrorMessage, + defaultTestGenErrorMessage, + noActiveFileErrorMessage, noSourceFilesErrorMessage, } from './constants' @@ -51,6 +55,12 @@ export class NoSourceFilesError extends SecurityScanError { } } +export class NoActiveFileError extends SecurityScanError { + constructor() { + super('Open valid file to run a file scan', 'NoActiveFileError', noActiveFileErrorMessage) + } +} + export class CreateUploadUrlError extends SecurityScanError { constructor(error: string) { super(error, 'CreateUploadUrlError', DefaultCodeScanErrorMessage) @@ -86,3 +96,79 @@ export class CodeScanJobFailedError extends SecurityScanError { super('Security scan failed.', 'CodeScanJobFailedError', DefaultCodeScanErrorMessage) } } + +export class MaximumFileScanReachedError extends SecurityScanError { + constructor() { + super( + 'Maximum file review count reached for this month.', + 'MaximumFileScanReachedError', + i18n('AWS.amazonq.featureDev.error.monthlyLimitReached') + ) + } +} + +export class MaximumProjectScanReachedError extends SecurityScanError { + constructor() { + super( + 'Maximum project review count reached for this month', + 'MaximumProjectScanReachedError', + i18n('AWS.amazonq.featureDev.error.monthlyLimitReached') + ) + } +} + +export class TestGenError extends ToolkitError { + constructor( + error: string, + code: string, + public customerFacingMessage: string + ) { + super(error, { code }) + } +} + +export class TestGenTimedOutError extends TestGenError { + constructor() { + super('Test generation failed. Amazon Q timed out.', 'TestGenTimedOutError', defaultTestGenErrorMessage) + } +} + +export class TestGenStoppedError extends TestGenError { + constructor() { + super('Test generation stopped by user.', 'TestGenCancelled', defaultTestGenErrorMessage) + } +} + +export class TestGenFailedError extends TestGenError { + constructor(error?: string) { + super(error ?? 'Test generation failed', 'TestGenFailedError', defaultTestGenErrorMessage) + } +} + +export class CodeFixError extends ToolkitError { + constructor( + error: string, + code: string, + public customerFacingMessage: string + ) { + super(error, { code }) + } +} + +export class CreateCodeFixError extends CodeFixError { + constructor() { + super('Code fix generation failed', 'CreateCodeFixFailed', defaultCodeFixErrorMessage) + } +} + +export class CodeFixJobTimedOutError extends CodeFixError { + constructor() { + super('Code fix generation failed. Amazon Q timed out.', 'CodeFixTimedOutError', defaultCodeFixErrorMessage) + } +} + +export class CodeFixJobStoppedError extends CodeFixError { + constructor() { + super('Code fix generation stopped by user.', 'CodeFixCancelled', defaultCodeFixErrorMessage) + } +} diff --git a/packages/core/src/codewhisperer/models/model.ts b/packages/core/src/codewhisperer/models/model.ts index 7e649d75e78..8e8e58d5fea 100644 --- a/packages/core/src/codewhisperer/models/model.ts +++ b/packages/core/src/codewhisperer/models/model.ts @@ -4,7 +4,7 @@ */ import * as vscode from 'vscode' import { ToolkitError } from '../../shared/errors' -import { getIcon } from '../../shared/icons' +import { getIcon, Icon } from '../../shared/icons' import { CodewhispererCodeScanScope, CodewhispererCompletionType, @@ -17,6 +17,9 @@ import { References } from '../client/codewhisperer' import globals from '../../shared/extensionGlobals' import { ChatControllerEventEmitters } from '../../amazonqGumby/chat/controller/controller' import { TransformationSteps } from '../client/codewhispereruserclient' +import { Messenger } from '../../amazonqGumby/chat/controller/messenger/messenger' +import { TestChatControllerEventEmitters } from '../../amazonqTest/chat/controller/controller' +import { ScanChatControllerEventEmitters } from '../../amazonqScan/controller' // unavoidable global variables interface VsCodeState { @@ -46,10 +49,20 @@ export const vsCodeState: VsCodeState = { export type UtgStrategy = 'ByName' | 'ByContent' -export type CrossFileStrategy = 'OpenTabs_BM25' +export type CrossFileStrategy = 'opentabs' | 'codemap' | 'bm25' | 'default' export type SupplementalContextStrategy = CrossFileStrategy | UtgStrategy | 'Empty' +export type PatchInfo = { + name: string + filename: string + isSuccessful: boolean +} + +export type DescriptionContent = { + content: PatchInfo[] +} + export interface CodeWhispererSupplementalContext { isUtg: boolean isProcessTimeout: boolean @@ -109,6 +122,136 @@ export class CodeSuggestionsState { } } +export interface AcceptedSuggestionEntry { + readonly time: Date + readonly fileUrl: vscode.Uri + readonly originalString: string + readonly startPosition: vscode.Position + readonly endPosition: vscode.Position + readonly requestId: string + readonly sessionId: string + readonly index: number + readonly triggerType: CodewhispererTriggerType + readonly completionType: CodewhispererCompletionType + readonly language: CodewhispererLanguage +} + +export interface OnRecommendationAcceptanceEntry { + readonly editor: vscode.TextEditor | undefined + readonly range: vscode.Range + readonly effectiveRange: vscode.Range + readonly acceptIndex: number + readonly recommendation: string + readonly requestId: string + readonly sessionId: string + readonly triggerType: CodewhispererTriggerType + readonly completionType: CodewhispererCompletionType + readonly language: CodewhispererLanguage + readonly references: References | undefined +} + +export interface ConfigurationEntry { + readonly isShowMethodsEnabled: boolean + readonly isManualTriggerEnabled: boolean + readonly isAutomatedTriggerEnabled: boolean + readonly isSuggestionsWithCodeReferencesEnabled: boolean +} + +export interface InlineCompletionItem { + content: string + index: number +} + +/** + * Q Security Scans + */ + +enum ScanStatus { + NotStarted, + Running, + Cancelling, +} + +type IconPath = { light: vscode.Uri; dark: vscode.Uri; toString: () => string } | Icon + +abstract class BaseScanState { + protected scanState: ScanStatus = ScanStatus.NotStarted + + protected chatControllers: ScanChatControllerEventEmitters | undefined = undefined + + public isNotStarted(): boolean { + return this.scanState === ScanStatus.NotStarted + } + + public isRunning(): boolean { + return this.scanState === ScanStatus.Running + } + + public isCancelling(): boolean { + return this.scanState === ScanStatus.Cancelling + } + + public setToNotStarted(): void { + this.scanState = ScanStatus.NotStarted + } + + public setToCancelling(): void { + this.scanState = ScanStatus.Cancelling + } + + public setToRunning(): void { + this.scanState = ScanStatus.Running + } + + public getPrefixTextForButton(): string { + switch (this.scanState) { + case ScanStatus.NotStarted: + return 'Run' + case ScanStatus.Running: + return 'Stop' + case ScanStatus.Cancelling: + return 'Stopping' + } + } + + public setChatControllers(controllers: ScanChatControllerEventEmitters) { + this.chatControllers = controllers + } + public getChatControllers() { + return this.chatControllers + } + + public abstract getIconForButton(): IconPath +} + +export class CodeScanState extends BaseScanState { + public getIconForButton(): IconPath { + switch (this.scanState) { + case ScanStatus.NotStarted: + return getIcon('vscode-debug-all') + case ScanStatus.Running: + return getIcon('vscode-stop-circle') + case ScanStatus.Cancelling: + return getIcon('vscode-loading~spin') + } + } +} + +export class OnDemandFileScanState extends BaseScanState { + public getIconForButton(): IconPath { + switch (this.scanState) { + case ScanStatus.NotStarted: + return getIcon('vscode-debug-all') + case ScanStatus.Running: + return getIcon('vscode-stop-circle') + case ScanStatus.Cancelling: + return getIcon('vscode-icons:loading~spin') + } + } +} +export const codeScanState: CodeScanState = new CodeScanState() +export const onDemandFileScanState: OnDemandFileScanState = new OnDemandFileScanState() + export class CodeScansState { /** The initial state if scan state was not defined */ #fallback: boolean @@ -164,113 +307,266 @@ export class CodeScansState { } } -export interface AcceptedSuggestionEntry { - readonly time: Date - readonly fileUrl: vscode.Uri - readonly originalString: string - readonly startPosition: vscode.Position - readonly endPosition: vscode.Position - readonly requestId: string - readonly sessionId: string - readonly index: number - readonly triggerType: CodewhispererTriggerType - readonly completionType: CodewhispererCompletionType - readonly language: CodewhispererLanguage +export class CodeScanStoppedError extends ToolkitError { + constructor() { + super('Security scan stopped by user.', { cancelled: true }) + } } -export interface OnRecommendationAcceptanceEntry { - readonly editor: vscode.TextEditor | undefined - readonly range: vscode.Range - readonly effectiveRange: vscode.Range - readonly acceptIndex: number - readonly recommendation: string - readonly requestId: string - readonly sessionId: string - readonly triggerType: CodewhispererTriggerType - readonly completionType: CodewhispererCompletionType - readonly language: CodewhispererLanguage - readonly references: References | undefined +export interface CodeScanTelemetryEntry extends MetricBase { + codewhispererCodeScanJobId?: string + codewhispererLanguage: CodewhispererLanguage + codewhispererCodeScanProjectBytes?: number + codewhispererCodeScanSrcPayloadBytes: number + codewhispererCodeScanBuildPayloadBytes?: number + codewhispererCodeScanSrcZipFileBytes: number + codewhispererCodeScanBuildZipFileBytes?: number + codewhispererCodeScanLines: number + duration: number + contextTruncationDuration: number + artifactsUploadDuration: number + codeScanServiceInvocationsDuration: number + result: Result + reason?: string + reasonDesc?: string + codewhispererCodeScanTotalIssues: number + codewhispererCodeScanIssuesWithFixes: number + credentialStartUrl: string | undefined + codewhispererCodeScanScope: CodewhispererCodeScanScope + source?: string } -export interface ConfigurationEntry { - readonly isShowMethodsEnabled: boolean - readonly isManualTriggerEnabled: boolean - readonly isAutomatedTriggerEnabled: boolean - readonly isSuggestionsWithCodeReferencesEnabled: boolean +export interface RecommendationDescription { + text: string + markdown: string } -export interface InlineCompletionItem { +export interface Recommendation { + text: string + url: string +} + +export interface SuggestedFix { + description: string + code?: string + references?: References +} + +export interface Remediation { + recommendation: Recommendation + suggestedFixes: SuggestedFix[] +} + +export interface CodeLine { content: string - index: number + number: number } /** - * Security Scan Interfaces + * Unit Test Generation */ -enum CodeScanStatus { +enum TestGenStatus { NotStarted, Running, Cancelling, } - -export class CodeScanState { +// TODO: Refactor model of /scan and /test +export class TestGenState { // Define a constructor for this class - private codeScanState: CodeScanStatus = CodeScanStatus.NotStarted + private testGenState: TestGenStatus = TestGenStatus.NotStarted + + protected chatControllers: TestChatControllerEventEmitters | undefined = undefined public isNotStarted() { - return this.codeScanState === CodeScanStatus.NotStarted + return this.testGenState === TestGenStatus.NotStarted } public isRunning() { - return this.codeScanState === CodeScanStatus.Running + return this.testGenState === TestGenStatus.Running } public isCancelling() { - return this.codeScanState === CodeScanStatus.Cancelling + return this.testGenState === TestGenStatus.Cancelling } public setToNotStarted() { - this.codeScanState = CodeScanStatus.NotStarted + this.testGenState = TestGenStatus.NotStarted } public setToCancelling() { - this.codeScanState = CodeScanStatus.Cancelling + this.testGenState = TestGenStatus.Cancelling } public setToRunning() { - this.codeScanState = CodeScanStatus.Running + this.testGenState = TestGenStatus.Running } - public getPrefixTextForButton() { - switch (this.codeScanState) { - case CodeScanStatus.NotStarted: - return 'Run' - case CodeScanStatus.Running: - return 'Stop' - case CodeScanStatus.Cancelling: - return 'Stopping' - } + public setChatControllers(controllers: TestChatControllerEventEmitters) { + this.chatControllers = controllers + } + public getChatControllers() { + return this.chatControllers } +} - public getIconForButton() { - switch (this.codeScanState) { - case CodeScanStatus.NotStarted: - return getIcon('vscode-debug-all') - case CodeScanStatus.Running: - return getIcon('vscode-stop-circle') - case CodeScanStatus.Cancelling: - return getIcon('vscode-loading~spin') - } +export const testGenState: TestGenState = new TestGenState() + +enum CodeFixStatus { + NotStarted, + Running, + Cancelling, +} + +export class CodeFixState { + // Define a constructor for this class + private codeFixState: CodeFixStatus = CodeFixStatus.NotStarted + + public isNotStarted() { + return this.codeFixState === CodeFixStatus.NotStarted + } + + public isRunning() { + return this.codeFixState === CodeFixStatus.Running + } + + public isCancelling() { + return this.codeFixState === CodeFixStatus.Cancelling + } + + public setToNotStarted() { + this.codeFixState = CodeFixStatus.NotStarted + } + + public setToCancelling() { + this.codeFixState = CodeFixStatus.Cancelling + } + + public setToRunning() { + this.codeFixState = CodeFixStatus.Running } } -export const codeScanState: CodeScanState = new CodeScanState() +export const codeFixState: CodeFixState = new CodeFixState() -export class CodeScanStoppedError extends ToolkitError { - constructor() { - super('Security scan stopped by user.', { cancelled: true }) +/** + * Security Scan Interfaces + */ + +export interface RawCodeScanIssue { + filePath: string + startLine: number + endLine: number + title: string + description: RecommendationDescription + detectorId: string + detectorName: string + findingId: string + ruleId?: string + relatedVulnerabilities: string[] + severity: string + remediation: Remediation + codeSnippet: CodeLine[] +} + +export interface CodeScanIssue { + startLine: number + endLine: number + comment: string + title: string + description: RecommendationDescription + detectorId: string + detectorName: string + findingId: string + ruleId?: string + relatedVulnerabilities: string[] + severity: string + recommendation: Recommendation + suggestedFixes: SuggestedFix[] + visible: boolean + scanJobId: string + language: string + fixJobId?: string +} + +export interface AggregatedCodeScanIssue { + filePath: string + issues: CodeScanIssue[] +} + +export interface SecurityPanelItem { + path: string + range: vscode.Range + severity: vscode.DiagnosticSeverity + message: string + issue: CodeScanIssue + decoration: vscode.DecorationOptions +} + +export interface SecurityPanelSet { + path: string + uri: vscode.Uri + items: SecurityPanelItem[] +} + +export const severities = ['Critical', 'High', 'Medium', 'Low', 'Info'] as const +export type Severity = (typeof severities)[number] + +export interface SecurityIssueFilters { + severity: { + Critical: boolean + High: boolean + Medium: boolean + Low: boolean + Info: boolean } } +const defaultVisibilityState: SecurityIssueFilters = { + severity: { + Critical: true, + High: true, + Medium: true, + Low: true, + Info: true, + }, +} + +export class SecurityTreeViewFilterState { + #fallback: SecurityIssueFilters + #onDidChangeState = new vscode.EventEmitter() + onDidChangeState = this.#onDidChangeState.event + + static #instance: SecurityTreeViewFilterState + static get instance() { + return (this.#instance ??= new this()) + } + + protected constructor(fallback: SecurityIssueFilters = defaultVisibilityState) { + this.#fallback = fallback + } + + public getState(): SecurityIssueFilters { + return globals.globalState.tryGet('aws.amazonq.securityIssueFilters', Object) ?? this.#fallback + } + + public async setState(state: SecurityIssueFilters) { + await globals.globalState.update('aws.amazonq.securityIssueFilters', state) + this.#onDidChangeState.fire(state) + } + + public getHiddenSeverities() { + return Object.entries(this.getState().severity) + .filter(([_, value]) => !value) + .map(([key]) => key) + } + + public resetFilters() { + return this.setState(defaultVisibilityState) + } +} + +/** + * Q - Transform + */ // for internal use; store status of job export enum TransformByQStatus { @@ -283,6 +579,11 @@ export enum TransformByQStatus { PartiallySucceeded = 'Partially Succeeded', } +export enum TransformationType { + LANGUAGE_UPGRADE = 'Language Upgrade', + SQL_CONVERSION = 'SQL Conversion', +} + export enum TransformByQReviewStatus { NotStarted = 'NotStarted', PreparingReview = 'PreparingReview', @@ -303,6 +604,13 @@ export enum JDKVersion { UNSUPPORTED = 'UNSUPPORTED', } +export enum DB { + ORACLE = 'ORACLE', + RDS_POSTGRESQL = 'POSTGRESQL', + AURORA_POSTGRESQL = 'AURORA_POSTGRESQL', + OTHER = 'OTHER', +} + export enum BuildSystem { Maven = 'Maven', Gradle = 'Gradle', @@ -311,12 +619,21 @@ export enum BuildSystem { export class ZipManifest { sourcesRoot: string = 'sources/' - dependenciesRoot: string | undefined = 'dependencies/' + dependenciesRoot: string = 'dependencies/' buildLogs: string = 'build-logs.txt' version: string = '1.0' hilCapabilities: string[] = ['HIL_1pDependency_VersionUpgrade'] transformCapabilities: string[] = ['EXPLAINABILITY_V1'] customBuildCommand: string = 'clean test' + requestedConversions?: { + sqlConversion?: { + source?: string + target?: string + schema?: string + host?: string + sctFileName?: string + } + } } export interface IHilZipManifestParams { @@ -364,6 +681,8 @@ export let sessionJobHistory: { export class TransformByQState { private transformByQState: TransformByQStatus = TransformByQStatus.NotStarted + private transformationType: TransformationType | undefined = undefined + private projectName: string = '' private projectPath: string = '' @@ -375,8 +694,24 @@ export class TransformByQState { private targetJDKVersion: JDKVersion = JDKVersion.JDK17 + private produceMultipleDiffs: boolean = false + private customBuildCommand: string = '' + private sourceDB: DB | undefined = undefined + + private targetDB: DB | undefined = undefined + + private schema: string = '' + + private schemaOptions: Set = new Set() + + private sourceServerName: string = '' + + private metadataPathSQL: string = '' + + private linesOfCodeSubmitted: number | undefined = undefined + private planFilePath: string = '' private summaryFilePath: string = '' private preBuildLogFilePath: string = '' @@ -401,6 +736,7 @@ export class TransformByQState { private javaHome: string | undefined = undefined private chatControllers: ChatControllerEventEmitters | undefined = undefined + private chatMessenger: Messenger | undefined = undefined private dependencyFolderInfo: FolderInfo | undefined = undefined @@ -432,6 +768,10 @@ export class TransformByQState { return this.transformByQState === TransformByQStatus.PartiallySucceeded } + public getTransformationType() { + return this.transformationType + } + public getProjectName() { return this.projectName } @@ -444,6 +784,14 @@ export class TransformByQState { return this.customBuildCommand } + public getLinesOfCodeSubmitted() { + return this.linesOfCodeSubmitted + } + + public getMultipleDiffs() { + return this.produceMultipleDiffs + } + public getPreBuildLogFilePath() { return this.preBuildLogFilePath } @@ -464,6 +812,30 @@ export class TransformByQState { return this.targetJDKVersion } + public getSourceDB() { + return this.sourceDB + } + + public getTargetDB() { + return this.targetDB + } + + public getSchema() { + return this.schema + } + + public getSchemaOptions() { + return this.schemaOptions + } + + public getSourceServerName() { + return this.sourceServerName + } + + public getMetadataPathSQL() { + return this.metadataPathSQL + } + public getStatus() { return this.transformByQState } @@ -520,6 +892,10 @@ export class TransformByQState { return this.chatControllers } + public getChatMessenger() { + return this.chatMessenger + } + public getDependencyFolderInfo(): FolderInfo | undefined { return this.dependencyFolderInfo } @@ -560,6 +936,10 @@ export class TransformByQState { this.transformByQState = TransformByQStatus.PartiallySucceeded } + public setTransformationType(type: TransformationType) { + this.transformationType = type + } + public setProjectName(name: string) { this.projectName = name } @@ -572,6 +952,14 @@ export class TransformByQState { this.customBuildCommand = command } + public setLinesOfCodeSubmitted(lines: number) { + this.linesOfCodeSubmitted = lines + } + + public setMultipleDiffs(produceMultipleDiffs: boolean) { + this.produceMultipleDiffs = produceMultipleDiffs + } + public setStartTime(time: string) { this.startTime = time } @@ -588,6 +976,30 @@ export class TransformByQState { this.targetJDKVersion = version } + public setSourceDB(db: DB) { + this.sourceDB = db + } + + public setTargetDB(db: DB) { + this.targetDB = db + } + + public setSchema(schema: string) { + this.schema = schema + } + + public setSchemaOptions(schemaOptions: Set) { + this.schemaOptions = schemaOptions + } + + public setSourceServerName(serverName: string) { + this.sourceServerName = serverName + } + + public setMetadataPathSQL(path: string) { + this.metadataPathSQL = path + } + public setPlanFilePath(filePath: string) { this.planFilePath = filePath } @@ -636,6 +1048,10 @@ export class TransformByQState { this.chatControllers = controllers } + public setChatMessenger(messenger: Messenger) { + this.chatMessenger = messenger + } + public setDependencyFolderInfo(folderInfo: FolderInfo) { this.dependencyFolderInfo = folderInfo } @@ -666,9 +1082,18 @@ export class TransformByQState { this.jobFailureErrorChatMessage = undefined this.jobFailureMetadata = '' this.payloadFilePath = '' + this.metadataPathSQL = '' + this.sourceJDKVersion = undefined + this.targetJDKVersion = JDKVersion.JDK17 + this.sourceDB = undefined + this.targetDB = undefined + this.sourceServerName = '' + this.schemaOptions.clear() + this.schema = '' this.errorLog = '' this.customBuildCommand = '' this.intervalId = undefined + this.produceMultipleDiffs = false } } @@ -680,105 +1105,6 @@ export class TransformByQStoppedError extends ToolkitError { } } -export interface CodeScanTelemetryEntry extends MetricBase { - codewhispererCodeScanJobId?: string - codewhispererLanguage: CodewhispererLanguage - codewhispererCodeScanProjectBytes?: number - codewhispererCodeScanSrcPayloadBytes: number - codewhispererCodeScanBuildPayloadBytes?: number - codewhispererCodeScanSrcZipFileBytes: number - codewhispererCodeScanBuildZipFileBytes?: number - codewhispererCodeScanLines: number - duration: number - contextTruncationDuration: number - artifactsUploadDuration: number - codeScanServiceInvocationsDuration: number - result: Result - reason?: string - reasonDesc?: string - codewhispererCodeScanTotalIssues: number - codewhispererCodeScanIssuesWithFixes: number - credentialStartUrl: string | undefined - codewhispererCodeScanScope: CodewhispererCodeScanScope -} - -export interface RecommendationDescription { - text: string - markdown: string -} - -export interface Recommendation { - text: string - url: string -} - -export interface SuggestedFix { - description: string - code: string -} - -export interface Remediation { - recommendation: Recommendation - suggestedFixes: SuggestedFix[] -} - -export interface CodeLine { - content: string - number: number -} - -export interface RawCodeScanIssue { - filePath: string - startLine: number - endLine: number - title: string - description: RecommendationDescription - detectorId: string - detectorName: string - findingId: string - ruleId?: string - relatedVulnerabilities: string[] - severity: string - remediation: Remediation - codeSnippet: CodeLine[] -} - -export interface CodeScanIssue { - startLine: number - endLine: number - comment: string - title: string - description: RecommendationDescription - detectorId: string - detectorName: string - findingId: string - ruleId?: string - relatedVulnerabilities: string[] - severity: string - recommendation: Recommendation - suggestedFixes: SuggestedFix[] -} - -export interface AggregatedCodeScanIssue { - filePath: string - issues: CodeScanIssue[] -} - -export interface SecurityPanelItem { - path: string - range: vscode.Range - severity: vscode.DiagnosticSeverity - message: string - issue: CodeScanIssue - decoration: vscode.DecorationOptions -} - -export interface SecurityPanelSet { - path: string - uri: vscode.Uri - items: SecurityPanelItem[] -} - export enum Cloud9AccessState { NoAccess, RequestedAccess, @@ -795,3 +1121,27 @@ export interface FolderInfo { path: string name: string } + +export interface ShortAnswerReference { + licenseName?: string + repository?: string + url?: string + recommendationContentSpan?: { + start: number + end: number + } +} + +export interface ShortAnswer { + testFilePath: string + buildCommands: string[] + planSummary: string + sourceFilePath?: string + testFramework?: string + executionCommands?: string[] + testCoverage?: number + stopIteration?: string + errorMessage?: string + codeReferences?: ShortAnswerReference[] + numberOfTestMethods?: number +} diff --git a/packages/core/src/codewhisperer/service/codeFixHandler.ts b/packages/core/src/codewhisperer/service/codeFixHandler.ts new file mode 100644 index 00000000000..0358d8d3ed9 --- /dev/null +++ b/packages/core/src/codewhisperer/service/codeFixHandler.ts @@ -0,0 +1,113 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CodeWhispererUserClient } from '../indexNode' +import * as CodeWhispererConstants from '../models/constants' +import { codeFixState } from '../models/model' +import { getLogger, sleep } from '../../shared' +import { ArtifactMap, CreateUploadUrlRequest, DefaultCodeWhispererClient } from '../client/codewhisperer' +import { + CodeFixJobStoppedError, + CodeFixJobTimedOutError, + CreateCodeFixError, + CreateUploadUrlError, +} from '../models/errors' +import { uploadArtifactToS3 } from './securityScanHandler' + +export async function getPresignedUrlAndUpload( + client: DefaultCodeWhispererClient, + zipFilePath: string, + codeFixName: string +) { + const srcReq: CreateUploadUrlRequest = { + artifactType: 'SourceCode', + uploadIntent: CodeWhispererConstants.codeFixUploadIntent, + uploadContext: { codeFixUploadContext: { codeFixName } }, + } + getLogger().verbose(`Prepare for uploading src context...`) + const srcResp = await client.createUploadUrl(srcReq).catch((err) => { + getLogger().error(`Failed getting presigned url for uploading src context. Request id: ${err.requestId}`) + throw new CreateUploadUrlError(err) + }) + getLogger().verbose(`CreateUploadUrlRequest requestId: ${srcResp.$response.requestId}`) + getLogger().verbose(`Complete Getting presigned Url for uploading src context.`) + getLogger().verbose(`Uploading src context...`) + await uploadArtifactToS3(zipFilePath, srcResp) + getLogger().verbose(`Complete uploading src context.`) + const artifactMap: ArtifactMap = { + SourceCode: srcResp.uploadId, + } + return artifactMap +} + +export async function createCodeFixJob( + client: DefaultCodeWhispererClient, + uploadId: string, + snippetRange: CodeWhispererUserClient.Range, + description: string, + codeFixName?: string, + ruleId?: string +) { + getLogger().verbose(`Creating code fix job...`) + const req: CodeWhispererUserClient.StartCodeFixJobRequest = { + uploadId, + snippetRange, + codeFixName, + ruleId, + description, + } + + const resp = await client.startCodeFixJob(req).catch((err) => { + getLogger().error(`Failed creating code fix job. Request id: ${err.requestId}`) + throw new CreateCodeFixError() + }) + getLogger().info(`AmazonQ generate fix Request id: ${resp.$response.requestId}`) + return resp +} + +export async function pollCodeFixJobStatus(client: DefaultCodeWhispererClient, jobId: string) { + const pollingStartTime = performance.now() + await sleep(CodeWhispererConstants.codeFixJobPollingDelayMs) + + getLogger().verbose(`Polling code fix job status...`) + let status: string | undefined = 'InProgress' + while (true) { + throwIfCancelled() + const req: CodeWhispererUserClient.GetCodeFixJobRequest = { + jobId, + } + const resp = await client.getCodeFixJob(req) + getLogger().verbose(`GetCodeFixJobRequest requestId: ${resp.$response.requestId}`) + if (resp.jobStatus !== 'InProgress') { + status = resp.jobStatus + getLogger().verbose(`Code fix job status: ${status}`) + getLogger().verbose(`Complete polling code fix job status.`) + break + } + throwIfCancelled() + await sleep(CodeWhispererConstants.codeFixJobPollingIntervalMs) + const elapsedTime = performance.now() - pollingStartTime + if (elapsedTime > CodeWhispererConstants.codeFixJobTimeoutMs) { + getLogger().verbose(`Code fix job status: ${status}`) + getLogger().verbose(`Code fix job failed. Amazon Q timed out.`) + throw new CodeFixJobTimedOutError() + } + } + return status +} + +export async function getCodeFixJob(client: DefaultCodeWhispererClient, jobId: string) { + const req: CodeWhispererUserClient.GetCodeFixJobRequest = { + jobId, + } + const resp = await client.getCodeFixJob(req) + return resp +} + +export function throwIfCancelled() { + if (codeFixState.isCancelling()) { + throw new CodeFixJobStoppedError() + } +} diff --git a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts index 97f44e0665f..3fcc9cb8b7d 100644 --- a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts +++ b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts @@ -5,9 +5,9 @@ import * as vscode from 'vscode' import { CodeScanIssue, AggregatedCodeScanIssue, CodeScansState } from '../models/model' -import { SecurityIssueHoverProvider } from './securityIssueHoverProvider' -import { SecurityIssueCodeActionProvider } from './securityIssueCodeActionProvider' import { CodeAnalysisScope, codewhispererDiagnosticSourceLabel } from '../models/constants' +import { SecurityIssueTreeViewProvider } from './securityIssueTreeViewProvider' +import { SecurityIssueProvider } from './securityIssueProvider' export interface SecurityDiagnostic extends vscode.Diagnostic { findingId?: string @@ -30,27 +30,25 @@ export function initSecurityScanRender( scope: CodeAnalysisScope ) { securityScanRender.initialized = false - if (scope === CodeAnalysisScope.FILE && editor) { + if ((scope === CodeAnalysisScope.FILE_AUTO || scope === CodeAnalysisScope.FILE_ON_DEMAND) && editor) { securityScanRender.securityDiagnosticCollection?.delete(editor.document.uri) } else if (scope === CodeAnalysisScope.PROJECT) { securityScanRender.securityDiagnosticCollection?.clear() } securityRecommendationList.forEach((securityRecommendation) => { updateSecurityDiagnosticCollection(securityRecommendation) - updateSecurityIssueHoverAndCodeActions(securityRecommendation) + updateSecurityIssuesForProviders(securityRecommendation) }) securityScanRender.initialized = true } -function updateSecurityIssueHoverAndCodeActions(securityRecommendation: AggregatedCodeScanIssue) { +function updateSecurityIssuesForProviders(securityRecommendation: AggregatedCodeScanIssue) { const updatedSecurityRecommendationList = [ - ...SecurityIssueHoverProvider.instance.issues.filter( - (group) => group.filePath !== securityRecommendation.filePath - ), + ...SecurityIssueProvider.instance.issues.filter((group) => group.filePath !== securityRecommendation.filePath), securityRecommendation, ] - SecurityIssueHoverProvider.instance.issues = updatedSecurityRecommendationList - SecurityIssueCodeActionProvider.instance.issues = updatedSecurityRecommendationList + SecurityIssueProvider.instance.issues = updatedSecurityRecommendationList + SecurityIssueTreeViewProvider.instance.refresh() } export function updateSecurityDiagnosticCollection(securityRecommendation: AggregatedCodeScanIssue) { @@ -60,9 +58,11 @@ export function updateSecurityDiagnosticCollection(securityRecommendation: Aggre const securityDiagnostics: vscode.Diagnostic[] = vscode.languages .getDiagnostics(uri) .filter((diagnostic) => diagnostic.source === codewhispererDiagnosticSourceLabel) - securityRecommendation.issues.forEach((securityIssue) => { - securityDiagnostics.push(createSecurityDiagnostic(securityIssue)) - }) + securityRecommendation.issues + .filter((securityIssue) => securityIssue.visible) + .forEach((securityIssue) => { + securityDiagnostics.push(createSecurityDiagnostic(securityIssue)) + }) securityDiagnosticCollection.set(uri, securityDiagnostics) } @@ -74,13 +74,7 @@ export function createSecurityDiagnostic(securityIssue: CodeScanIssue) { vscode.DiagnosticSeverity.Warning ) securityDiagnostic.source = codewhispererDiagnosticSourceLabel - const detectorUrl = securityIssue.recommendation.url - securityDiagnostic.code = detectorUrl - ? { - value: securityIssue.detectorId, - target: vscode.Uri.parse(detectorUrl), - } - : securityIssue.detectorId + securityDiagnostic.code = securityIssue.ruleId securityDiagnostic.findingId = securityIssue.findingId return securityDiagnostic } diff --git a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts index b2571ece38c..e5ac2212e06 100644 --- a/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts +++ b/packages/core/src/codewhisperer/service/inlineCompletionItemProvider.ts @@ -169,6 +169,7 @@ export class CWInlineCompletionItemProvider implements vscode.InlineCompletionIt ImportAdderProvider.instance.onShowRecommendation(document, this.startPos.line, r) this.nextMove = 0 TelemetryHelper.instance.setFirstSuggestionShowTime() + session.setPerceivedLatency() this._onDidShow.fire() if (matchedCount >= 2 || this.nextToken !== '') { const result = [item] diff --git a/packages/core/src/codewhisperer/service/inlineCompletionService.ts b/packages/core/src/codewhisperer/service/inlineCompletionService.ts index 69bd9bdb887..715fd93ad2d 100644 --- a/packages/core/src/codewhisperer/service/inlineCompletionService.ts +++ b/packages/core/src/codewhisperer/service/inlineCompletionService.ts @@ -112,7 +112,6 @@ export class InlineCompletionService { await this.setState('loading') - TelemetryHelper.instance.setInvocationStartTime(performance.now()) RecommendationHandler.instance.checkAndResetCancellationTokens() RecommendationHandler.instance.documentUri = editor.document.uri let response: GetRecommendationsResponse = { diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts index fcd7fb2b84a..bbcd177a03b 100644 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts @@ -14,7 +14,7 @@ import { AWSError } from 'aws-sdk' import { isAwsError } from '../../shared/errors' import { TelemetryHelper } from '../util/telemetryHelper' import { getLogger } from '../../shared/logger' -import { isCloud9, isSageMaker } from '../../shared/extensionUtilities' +import { isCloud9 } from '../../shared/extensionUtilities' import { hasVendedIamCredentials } from '../../auth/auth' import { asyncCallWithTimeout, @@ -38,12 +38,12 @@ import globals from '../../shared/extensionGlobals' import { noSuggestions, updateInlineLockKey } from '../models/constants' import AsyncLock from 'async-lock' import { AuthUtil } from '../util/authUtil' -import { CodeWhispererUserGroupSettings } from '../util/userGroupUtil' import { CWInlineCompletionItemProvider } from './inlineCompletionItemProvider' import { application } from '../util/codeWhispererApplication' import { openUrl } from '../../shared/utilities/vsCodeUtils' import { indent } from '../../shared/utilities/textUtilities' import path from 'path' +import { isIamConnection } from '../../auth/connection' /** * This class is for getRecommendation/listRecommendation API calls and its states @@ -159,8 +159,7 @@ export class RecommendationHandler { autoTriggerType?: CodewhispererAutomatedTriggerType, pagination: boolean = true, page: number = 0, - isSM: boolean = isSageMaker(), - retry: boolean = false + generate: boolean = isIamConnection(AuthUtil.instance.conn) ): Promise { let invocationResult: 'Succeeded' | 'Failed' = 'Failed' let errorMessage: string | undefined = undefined @@ -187,7 +186,7 @@ export class RecommendationHandler { ).language session.taskType = await this.getTaskTypeFromEditorFileName(editor.document.fileName) - if (pagination && !isSM) { + if (pagination && !generate) { if (page === 0) { session.requestContext = await EditorContext.buildListRecommendationRequest( editor as vscode.TextEditor, @@ -221,10 +220,7 @@ export class RecommendationHandler { * Validate request */ if (!EditorContext.validateRequest(request)) { - getLogger().verbose( - 'Invalid Request : ', - JSON.stringify(request, undefined, EditorContext.getTabSize()) - ) + getLogger().verbose('Invalid Request: %O', request) const languageName = request.fileContext.programmingLanguage.languageName if (!runtimeLanguageContext.isLanguageSupported(languageName)) { errorMessage = `${languageName} is currently not supported by Amazon Q inline suggestions` @@ -242,7 +238,9 @@ export class RecommendationHandler { this.lastInvocationTime = startTime const mappedReq = runtimeLanguageContext.mapToRuntimeLanguage(request) const codewhispererPromise = - pagination && !isSM ? client.listRecommendations(mappedReq) : client.generateRecommendations(mappedReq) + pagination && !generate + ? client.listRecommendations(mappedReq) + : client.generateRecommendations(mappedReq) const resp = await this.getServerResponse(triggerType, config.isManualTriggerEnabled, codewhispererPromise) TelemetryHelper.instance.setSdkApiCallEndTime() latency = startTime !== 0 ? performance.now() - startTime : 0 @@ -257,7 +255,7 @@ export class RecommendationHandler { sessionId = resp?.$response?.httpResponse?.headers['x-amzn-sessionid'] TelemetryHelper.instance.setFirstResponseRequestId(requestId) if (page === 0) { - TelemetryHelper.instance.setTimeToFirstRecommendation(performance.now()) + session.setTimeToFirstRecommendation(performance.now()) } if (nextToken === '') { TelemetryHelper.instance.setAllPaginationEndTime() @@ -392,6 +390,7 @@ export class RecommendationHandler { // send Empty userDecision event if user receives no recommendations in this session at all. if (invocationResult === 'Succeeded' && nextToken === '') { + // case 1: empty list of suggestion [] if (session.recommendations.length === 0) { session.requestIdList.push(requestId) // Received an empty list of recommendations @@ -406,7 +405,8 @@ export class RecommendationHandler { session.requestContext.supplementalMetadata ) } - if (!this.hasAtLeastOneValidSuggestion(typedPrefix)) { + // case 2: non empty list of suggestion but with (a) empty content or (b) non-matching typeahead + else if (!this.hasAtLeastOneValidSuggestion(typedPrefix)) { this.reportUserDecisions(-1) } } @@ -702,7 +702,6 @@ export class RecommendationHandler { duration: performance.now() - this.lastInvocationTime, passive: true, credentialStartUrl: AuthUtil.instance.startUrl, - codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(), result: 'Succeeded', }) } diff --git a/packages/core/src/codewhisperer/service/securityIssueCodeActionProvider.ts b/packages/core/src/codewhisperer/service/securityIssueCodeActionProvider.ts index d093fd3fba1..f1d01494d54 100644 --- a/packages/core/src/codewhisperer/service/securityIssueCodeActionProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueCodeActionProvider.ts @@ -7,9 +7,11 @@ import * as vscode from 'vscode' import { SecurityIssueProvider } from './securityIssueProvider' import { CodeScanIssue } from '../models/model' import { Component } from '../../shared/telemetry/telemetry' +import { amazonqCodeIssueDetailsTabTitle } from '../models/constants' -export class SecurityIssueCodeActionProvider extends SecurityIssueProvider implements vscode.CodeActionProvider { +export class SecurityIssueCodeActionProvider implements vscode.CodeActionProvider { static #instance: SecurityIssueCodeActionProvider + private issueProvider = SecurityIssueProvider.instance public static get instance() { return (this.#instance ??= new this()) @@ -23,12 +25,15 @@ export class SecurityIssueCodeActionProvider extends SecurityIssueProvider imple ): vscode.CodeAction[] { const codeActions: vscode.CodeAction[] = [] - for (const group of this.issues) { + for (const group of this.issueProvider.issues) { if (document.fileName !== group.filePath) { continue } for (const issue of group.issues) { + if (!issue.visible) { + continue + } const issueRange = new vscode.Range(issue.startLine, 0, issue.endLine, 0) if (issueRange.contains(range)) { const [suggestedFix] = issue.suggestedFixes @@ -51,7 +56,7 @@ export class SecurityIssueCodeActionProvider extends SecurityIssueProvider imple ) const args: [CodeScanIssue, string] = [issue, group.filePath] openIssue.command = { - title: 'Open "Amazon Q Security Issue"', + title: `Open "${amazonqCodeIssueDetailsTabTitle}"`, command: 'aws.amazonq.openSecurityIssuePanel', arguments: args, } @@ -68,6 +73,30 @@ export class SecurityIssueCodeActionProvider extends SecurityIssueProvider imple arguments: explainWithQArgs, } codeActions.push(explainWithQ) + + const ignoreIssue = new vscode.CodeAction( + `Amazon Q: Ignore this "${issue.title}" issue`, + vscode.CodeActionKind.QuickFix + ) + const ignoreIssueArgs = [issue, group.filePath, 'quickfix'] + ignoreIssue.command = { + title: 'Ignore this issue', + command: 'aws.amazonq.security.ignore', + arguments: ignoreIssueArgs, + } + codeActions.push(ignoreIssue) + + const ignoreAllIssues = new vscode.CodeAction( + `Amazon Q: Ignore all "${issue.title}" issues`, + vscode.CodeActionKind.QuickFix + ) + const ignoreAllIssuesArgs = [issue, 'quickfix'] + ignoreAllIssues.command = { + title: 'Ignore similar issues', + command: 'aws.amazonq.security.ignoreAll', + arguments: ignoreAllIssuesArgs, + } + codeActions.push(ignoreAllIssues) } } } diff --git a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts index 7624ec2554b..d2ed5c210df 100644 --- a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts @@ -5,14 +5,16 @@ import * as vscode from 'vscode' import { CodeScanIssue } from '../models/model' import globals from '../../shared/extensionGlobals' -import { SecurityIssueProvider } from './securityIssueProvider' import { telemetry } from '../../shared/telemetry/telemetry' import path from 'path' import { AuthUtil } from '../util/authUtil' import { TelemetryHelper } from '../util/telemetryHelper' +import { SecurityIssueProvider } from './securityIssueProvider' +import { amazonqCodeIssueDetailsTabTitle } from '../models/constants' -export class SecurityIssueHoverProvider extends SecurityIssueProvider implements vscode.HoverProvider { +export class SecurityIssueHoverProvider implements vscode.HoverProvider { static #instance: SecurityIssueHoverProvider + private issueProvider: SecurityIssueProvider = SecurityIssueProvider.instance public static get instance() { return (this.#instance ??= new this()) @@ -25,12 +27,15 @@ export class SecurityIssueHoverProvider extends SecurityIssueProvider implements ): vscode.Hover { const contents: vscode.MarkdownString[] = [] - for (const group of this.issues) { + for (const group of this.issueProvider.issues) { if (document.fileName !== group.filePath) { continue } for (const issue of group.issues) { + if (!issue.visible) { + continue + } const range = new vscode.Range(issue.startLine, 0, issue.endLine, 0) if (range.contains(position)) { contents.push(this._getContent(group.filePath, issue)) @@ -69,14 +74,16 @@ export class SecurityIssueHoverProvider extends SecurityIssueProvider implements const [suggestedFix] = issue.suggestedFixes markdownString.appendMarkdown(`## ${issue.title} ${this._makeSeverityBadge(issue.severity)}\n`) - markdownString.appendMarkdown(`${suggestedFix ? suggestedFix.description : issue.recommendation.text}\n\n`) + markdownString.appendMarkdown( + `${suggestedFix?.code && suggestedFix.description !== '' ? suggestedFix.description : issue.recommendation.text}\n\n` + ) const viewDetailsCommand = this._getCommandMarkdown( 'aws.amazonq.openSecurityIssuePanel', [issue, filePath], 'eye', 'View Details', - 'Open "Amazon Q Security Issue"' + `Open "${amazonqCodeIssueDetailsTabTitle}"` ) markdownString.appendMarkdown(viewDetailsCommand) @@ -89,7 +96,25 @@ export class SecurityIssueHoverProvider extends SecurityIssueProvider implements ) markdownString.appendMarkdown(' | ' + explainWithQCommand) - if (suggestedFix) { + const ignoreIssueCommand = this._getCommandMarkdown( + 'aws.amazonq.security.ignore', + [issue, filePath, 'hover'], + 'error', + 'Ignore', + 'Ignore Issue' + ) + markdownString.appendMarkdown(' | ' + ignoreIssueCommand) + + const ignoreSimilarIssuesCommand = this._getCommandMarkdown( + 'aws.amazonq.security.ignoreAll', + [issue, 'hover'], + 'error', + 'Ignore All', + 'Ignore Similar Issues' + ) + markdownString.appendMarkdown(' | ' + ignoreSimilarIssuesCommand) + + if (suggestedFix && suggestedFix.code) { const applyFixCommand = this._getCommandMarkdown( 'aws.amazonq.applySecurityFix', [issue, filePath, 'hover'], diff --git a/packages/core/src/codewhisperer/service/securityIssueProvider.ts b/packages/core/src/codewhisperer/service/securityIssueProvider.ts index e666d5335f5..edd93de8433 100644 --- a/packages/core/src/codewhisperer/service/securityIssueProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueProvider.ts @@ -5,7 +5,12 @@ import * as vscode from 'vscode' import { AggregatedCodeScanIssue, CodeScanIssue, CodeScansState, SuggestedFix } from '../models/model' -export abstract class SecurityIssueProvider { +export class SecurityIssueProvider { + static #instance: SecurityIssueProvider + public static get instance() { + return (this.#instance ??= new this()) + } + private _issues: AggregatedCodeScanIssue[] = [] public get issues() { return this._issues @@ -80,7 +85,7 @@ export abstract class SecurityIssueProvider { private _offsetSuggestedFix(suggestedFix: SuggestedFix, lines: number): SuggestedFix { return { ...suggestedFix, - code: suggestedFix.code.replace( + code: suggestedFix.code?.replace( /^(@@ -)(\d+)(,\d+ \+)(\d+)(,\d+ @@)/, function (_fullMatch, ...groups: string[]) { return ( @@ -92,6 +97,15 @@ export abstract class SecurityIssueProvider { ) } ), + references: + suggestedFix.references?.map((ref) => ({ + ...ref, + recommendationContentSpan: { + ...ref.recommendationContentSpan, + start: Number(ref.recommendationContentSpan?.start) + lines, + end: Number(ref.recommendationContentSpan?.end) + lines, + }, + })) ?? [], } } @@ -106,4 +120,16 @@ export abstract class SecurityIssueProvider { } }) } + + public updateIssue(issue: CodeScanIssue, filePath?: string) { + this._issues = this._issues.map((group) => { + if (filePath && group.filePath !== filePath) { + return group + } + return { + ...group, + issues: group.issues.map((i) => (i.findingId === issue.findingId ? issue : i)), + } + }) + } } diff --git a/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts b/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts new file mode 100644 index 00000000000..e76a201be87 --- /dev/null +++ b/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts @@ -0,0 +1,170 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import path from 'path' +import { CodeScanIssue, SecurityTreeViewFilterState, severities, Severity } from '../models/model' +import globals from '../../shared/extensionGlobals' +import { getLogger } from '../../shared/logger' +import { SecurityIssueProvider } from './securityIssueProvider' + +export type SecurityViewTreeItem = FileItem | IssueItem | SeverityItem +type CodeScanIssueWithFilePath = CodeScanIssue & { filePath: string } + +export class SecurityIssueTreeViewProvider implements vscode.TreeDataProvider { + public static readonly viewType = 'aws.amazonq.SecurityIssuesTree' + + private _onDidChangeTreeData: vscode.EventEmitter< + SecurityViewTreeItem | SecurityViewTreeItem[] | undefined | null | void + > = new vscode.EventEmitter() + readonly onDidChangeTreeData: vscode.Event< + SecurityViewTreeItem | SecurityViewTreeItem[] | undefined | null | void + > = this._onDidChangeTreeData.event + + static #instance: SecurityIssueTreeViewProvider + private issueProvider = SecurityIssueProvider.instance + + public static get instance() { + return (this.#instance ??= new this()) + } + + public getTreeItem(element: SecurityViewTreeItem): vscode.TreeItem | Thenable { + return element + } + + public getChildren(element?: SecurityViewTreeItem | undefined): vscode.ProviderResult { + const filterHiddenSeverities = (severity: Severity) => + !SecurityTreeViewFilterState.instance.getHiddenSeverities().includes(severity) + + if (element instanceof SeverityItem) { + return element.issues + .filter((issue) => issue.visible) + .sort((a, b) => a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine) + .map((issue) => new IssueItem(issue.filePath, issue)) + } + const result = severities.filter(filterHiddenSeverities).map( + (severity) => + new SeverityItem( + severity, + this.issueProvider.issues.reduce( + (accumulator, current) => + accumulator.concat( + current.issues + .filter((issue) => issue.severity === severity) + .filter((issue) => issue.visible) + .map((issue) => ({ ...issue, filePath: current.filePath })) + ), + [] as CodeScanIssueWithFilePath[] + ) + ) + ) + + this._onDidChangeTreeData.fire(result) + return result + } + + public refresh(): void { + this._onDidChangeTreeData.fire() + } +} + +enum ContextValue { + FILE = 'file', + ISSUE_WITH_FIX = 'issueWithFix', + ISSUE_WITHOUT_FIX = 'issueWithoutFix', + SEVERITY = 'severity', +} + +export class SeverityItem extends vscode.TreeItem { + constructor( + public readonly severity: string, + public readonly issues: CodeScanIssueWithFilePath[] + ) { + super(severity) + this.description = `${this.issues.length} ${this.issues.length === 1 ? 'issue' : 'issues'}` + this.iconPath = this.getSeverityIcon() + this.contextValue = ContextValue.SEVERITY + this.collapsibleState = this.getCollapsibleState() + } + + private getSeverityIcon() { + return globals.context.asAbsolutePath(`resources/icons/aws/amazonq/severity-${this.severity.toLowerCase()}.svg`) + } + + private getCollapsibleState() { + return this.severity === 'Critical' || this.severity === 'High' + ? vscode.TreeItemCollapsibleState.Expanded + : vscode.TreeItemCollapsibleState.Collapsed + } +} + +export class FileItem extends vscode.TreeItem { + constructor( + public readonly filePath: string, + public readonly issues: CodeScanIssue[] + ) { + super(path.basename(filePath), vscode.TreeItemCollapsibleState.Expanded) + this.resourceUri = vscode.Uri.file(this.filePath) + this.description = vscode.workspace.asRelativePath(path.dirname(this.filePath)) + this.iconPath = new vscode.ThemeIcon('file') + this.contextValue = ContextValue.FILE + } +} + +export class IssueItem extends vscode.TreeItem { + constructor( + public readonly filePath: string, + public readonly issue: CodeScanIssue + ) { + super(issue.title, vscode.TreeItemCollapsibleState.None) + this.description = `${path.basename(this.filePath)} [Ln ${this.issue.startLine + 1}, Col 1]` + this.tooltip = this.getTooltipMarkdown() + this.command = { + title: 'Focus Issue', + command: 'aws.amazonq.security.focusIssue', + arguments: [this.issue, this.filePath], + } + this.contextValue = this.getContextValue() + } + + private getSeverityImage() { + return globals.context.asAbsolutePath(`resources/images/severity-${this.issue.severity.toLowerCase()}.svg`) + } + + private getContextValue() { + return this.issue.suggestedFixes.length === 0 || !this.issue.suggestedFixes[0].code + ? ContextValue.ISSUE_WITHOUT_FIX + : ContextValue.ISSUE_WITH_FIX + } + + private getTooltipMarkdown() { + const markdown = new vscode.MarkdownString() + markdown.isTrusted = true + markdown.supportHtml = true + markdown.supportThemeIcons = true + markdown.appendMarkdown(`## ${this.issue.title} ![${this.issue.severity}](${this.getSeverityImage()})\n`) + markdown.appendMarkdown(this.issue.recommendation.text) + + return markdown + } +} + +export class SecurityIssuesTree { + static #instance: SecurityIssuesTree + public static get instance() { + return (this.#instance ??= new this()) + } + + constructor() { + vscode.window.createTreeView(SecurityIssueTreeViewProvider.viewType, { + treeDataProvider: SecurityIssueTreeViewProvider.instance, + }) + } + + public focus() { + void vscode.commands.executeCommand('aws.amazonq.SecurityIssuesTree.focus').then(undefined, (e) => { + getLogger().error('SecurityIssuesTree focus failed: %s', e.message) + }) + } +} diff --git a/packages/core/src/codewhisperer/service/securityScanHandler.ts b/packages/core/src/codewhisperer/service/securityScanHandler.ts index e746ad6e108..d328034d560 100644 --- a/packages/core/src/codewhisperer/service/securityScanHandler.ts +++ b/packages/core/src/codewhisperer/service/securityScanHandler.ts @@ -12,11 +12,12 @@ import { CodeScansState, codeScanState, CodeScanStoppedError, + onDemandFileScanState, } from '../models/model' import { sleep } from '../../shared/utilities/timeoutUtils' import * as codewhispererClient from '../client/codewhisperer' import * as CodeWhispererConstants from '../models/constants' -import { existsSync, statSync, readFileSync } from 'fs' +import { existsSync, statSync, readFileSync } from 'fs' // eslint-disable-line no-restricted-imports import { RawCodeScanIssue } from '../models/model' import * as crypto from 'crypto' import path = require('path') @@ -39,6 +40,9 @@ import { UploadArtifactToS3Error, } from '../models/errors' import { getTelemetryReasonDesc } from '../../shared/errors' +import { CodeWhispererSettings } from '../util/codewhispererSettings' +import { detectCommentAboveLine } from '../../shared/utilities/commentUtils' +import { runtimeLanguageContext } from '../util/runtimeLanguageContext' export async function listScanResults( client: DefaultCodeWhispererClient, @@ -52,11 +56,12 @@ export async function listScanResults( const codeScanIssueMap: Map = new Map() const aggregatedCodeScanIssueList: AggregatedCodeScanIssue[] = [] const requester = (request: codewhispererClient.ListCodeScanFindingsRequest) => client.listCodeScanFindings(request) - const collection = pageableToCollection(requester, { jobId, codeScanFindingsSchema }, 'nextToken') + const request: codewhispererClient.ListCodeScanFindingsRequest = { jobId, codeScanFindingsSchema } + const collection = pageableToCollection(requester, request, 'nextToken') const issues = await collection .flatten() .map((resp) => { - logger.verbose(`Request id: ${resp.$response.requestId}`) + logger.verbose(`ListCodeScanFindingsRequest requestId: ${resp.$response.requestId}`) if ('codeScanFindings' in resp) { return resp.codeScanFindings } @@ -76,7 +81,7 @@ export async function listScanResults( if (existsSync(filePath) && statSync(filePath).isFile()) { const aggregatedCodeScanIssue: AggregatedCodeScanIssue = { filePath: filePath, - issues: issues.map(mapRawToCodeScanIssue), + issues: issues.map((issue) => mapRawToCodeScanIssue(issue, editor, jobId)), } aggregatedCodeScanIssueList.push(aggregatedCodeScanIssue) } @@ -85,7 +90,7 @@ export async function listScanResults( if (existsSync(maybeAbsolutePath) && statSync(maybeAbsolutePath).isFile()) { const aggregatedCodeScanIssue: AggregatedCodeScanIssue = { filePath: maybeAbsolutePath, - issues: issues.map(mapRawToCodeScanIssue), + issues: issues.map((issue) => mapRawToCodeScanIssue(issue, editor, jobId)), } aggregatedCodeScanIssueList.push(aggregatedCodeScanIssue) } @@ -93,7 +98,19 @@ export async function listScanResults( return aggregatedCodeScanIssueList } -function mapRawToCodeScanIssue(issue: RawCodeScanIssue): CodeScanIssue { +function mapRawToCodeScanIssue( + issue: RawCodeScanIssue, + editor: vscode.TextEditor | undefined, + jobId: string +): CodeScanIssue { + const isIssueTitleIgnored = CodeWhispererSettings.instance.getIgnoredSecurityIssues().includes(issue.title) + const isSingleIssueIgnored = + editor && + detectCommentAboveLine(editor.document, issue.startLine - 1, CodeWhispererConstants.amazonqIgnoreNextLine) + const language = editor + ? runtimeLanguageContext.getLanguageContext(editor.document.languageId, path.extname(editor.document.fileName)) + .language + : 'plaintext' return { startLine: issue.startLine - 1 >= 0 ? issue.startLine - 1 : 0, endLine: issue.endLine, @@ -108,6 +125,9 @@ function mapRawToCodeScanIssue(issue: RawCodeScanIssue): CodeScanIssue { severity: issue.severity, recommendation: issue.remediation.recommendation, suggestedFixes: issue.remediation.suggestedFixes, + visible: !isIssueTitleIgnored && !isSingleIssueIgnored, + scanJobId: jobId, + language, } } @@ -119,7 +139,11 @@ export function mapToAggregatedList( ) { const codeScanIssues: RawCodeScanIssue[] = JSON.parse(json) const filteredIssues = codeScanIssues.filter((issue) => { - if (scope === CodeWhispererConstants.CodeAnalysisScope.FILE && editor) { + if ( + (scope === CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO || + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND) && + editor + ) { for (let lineNumber = issue.startLine; lineNumber <= issue.endLine; lineNumber++) { const line = editor.document.lineAt(lineNumber - 1)?.text const codeContent = issue.codeSnippet.find((codeIssue) => codeIssue.number === lineNumber)?.content @@ -137,13 +161,30 @@ export function mapToAggregatedList( filteredIssues.forEach((issue) => { const filePath = issue.filePath if (codeScanIssueMap.has(filePath)) { - codeScanIssueMap.get(filePath)?.push(issue) + if (!isExistingIssue(issue, codeScanIssueMap)) { + codeScanIssueMap.get(filePath)?.push(issue) + } else { + getLogger().warn('Found duplicate issue %O, ignoring...', issue) + } } else { codeScanIssueMap.set(filePath, [issue]) } }) } +function isDuplicateIssue(issueA: RawCodeScanIssue, issueB: RawCodeScanIssue) { + return ( + issueA.filePath === issueB.filePath && + issueA.title === issueB.title && + issueA.startLine === issueB.startLine && + issueA.endLine === issueB.endLine + ) +} + +function isExistingIssue(issue: RawCodeScanIssue, codeScanIssueMap: Map) { + return codeScanIssueMap.get(issue.filePath)?.some((existingIssue) => isDuplicateIssue(issue, existingIssue)) +} + export async function pollScanJobStatus( client: DefaultCodeWhispererClient, jobId: string, @@ -163,7 +204,7 @@ export async function pollScanJobStatus( jobId: jobId, } const resp = await client.getCodeScan(req) - logger.verbose(`Request id: ${resp.$response.requestId}`) + logger.verbose(`GetCodeScanRequest requestId: ${resp.$response.requestId}`) if (resp.status !== 'Pending') { status = resp.status logger.verbose(`Scan job status: ${status}`) @@ -191,19 +232,28 @@ export async function createScanJob( ) { const logger = getLoggerForScope(scope) logger.verbose(`Creating scan job...`) + const codeAnalysisScope = scope === CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO ? 'FILE' : 'PROJECT' const req: codewhispererClient.CreateCodeScanRequest = { artifacts: artifactMap, programmingLanguage: { languageName: languageId, }, - scope: scope, + scope: codeAnalysisScope, codeScanName: scanName, } const resp = await client.createCodeScan(req).catch((err) => { getLogger().error(`Failed creating scan job. Request id: ${err.requestId}`) + if ( + err.message === CodeWhispererConstants.scansLimitReachedErrorMessage && + err.code === 'ThrottlingException' + ) { + throw err + } throw new CreateCodeScanError(err) }) - logger.verbose(`Request id: ${resp.$response.requestId}`) + getLogger().info( + `Amazon Q Code Review requestId: ${resp.$response.requestId} and Amazon Q Code Review jobId: ${resp.jobId}` + ) TelemetryHelper.instance.sendCodeScanEvent(languageId, resp.$response.requestId) return resp } @@ -234,7 +284,7 @@ export async function getPresignedUrlAndUpload( getLogger().error(`Failed getting presigned url for uploading src context. Request id: ${err.requestId}`) throw new CreateUploadUrlError(err) }) - logger.verbose(`Request id: ${srcResp.$response.requestId}`) + logger.verbose(`CreateUploadUrlRequest request id: ${srcResp.$response.requestId}`) logger.verbose(`Complete Getting presigned Url for uploading src context.`) logger.verbose(`Uploading src context...`) await uploadArtifactToS3(zipMetadata.zipFilePath, srcResp, scope) @@ -246,12 +296,17 @@ export async function getPresignedUrlAndUpload( } function getUploadIntent(scope: CodeWhispererConstants.CodeAnalysisScope): UploadIntent { - return scope === CodeWhispererConstants.CodeAnalysisScope.FILE - ? CodeWhispererConstants.fileScanUploadIntent - : CodeWhispererConstants.projectScanUploadIntent + if ( + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO || + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND + ) { + return CodeWhispererConstants.fileScanUploadIntent + } else { + return CodeWhispererConstants.projectScanUploadIntent + } } -function getMd5(fileName: string) { +export function getMd5(fileName: string) { const hasher = crypto.createHash('md5') hasher.update(readFileSync(fileName)) return hasher.digest('base64') @@ -264,7 +319,12 @@ export function throwIfCancelled(scope: CodeWhispererConstants.CodeAnalysisScope throw new CodeScanStoppedError() } break - case CodeWhispererConstants.CodeAnalysisScope.FILE: { + case CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND: + if (onDemandFileScanState.isCancelling()) { + throw new CodeScanStoppedError() + } + break + case CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO: { const latestCodeScanStartTime = CodeScansState.instance.getLatestScanTime() if ( !CodeScansState.instance.isScansEnabled() || @@ -279,11 +339,11 @@ export function throwIfCancelled(scope: CodeWhispererConstants.CodeAnalysisScope break } } - +// TODO: Refactor this export async function uploadArtifactToS3( fileName: string, resp: CreateUploadUrlResponse, - scope: CodeWhispererConstants.CodeAnalysisScope + scope?: CodeWhispererConstants.CodeAnalysisScope ) { const logger = getLoggerForScope(scope) const encryptionContext = `{"uploadId":"${resp.uploadId}"}` @@ -310,19 +370,21 @@ export async function uploadArtifactToS3( ) const errorMessage = getTelemetryReasonDesc(error)?.includes(`"PUT" request failed with code "403"`) ? `"PUT" request failed with code "403"` - : getTelemetryReasonDesc(error) ?? 'Security scan failed.' + : (getTelemetryReasonDesc(error) ?? 'Security scan failed.') throw new UploadArtifactToS3Error(errorMessage) } } -export function getLoggerForScope(scope: CodeWhispererConstants.CodeAnalysisScope) { - return scope === CodeWhispererConstants.CodeAnalysisScope.FILE ? getNullLogger() : getLogger() +// TODO: Refactor this +export function getLoggerForScope(scope?: CodeWhispererConstants.CodeAnalysisScope) { + return scope === CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO ? getNullLogger() : getLogger() } function getPollingDelayMsForScope(scope: CodeWhispererConstants.CodeAnalysisScope) { return ( - (scope === CodeWhispererConstants.CodeAnalysisScope.FILE + (scope === CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO || + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND ? CodeWhispererConstants.fileScanPollingDelaySeconds : CodeWhispererConstants.projectScanPollingDelaySeconds) * 1000 ) @@ -330,7 +392,8 @@ function getPollingDelayMsForScope(scope: CodeWhispererConstants.CodeAnalysisSco function getPollingTimeoutMsForScope(scope: CodeWhispererConstants.CodeAnalysisScope) { return ( - (scope === CodeWhispererConstants.CodeAnalysisScope.FILE + (scope === CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO || + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND ? CodeWhispererConstants.codeFileScanJobTimeoutSeconds : CodeWhispererConstants.codeScanJobTimeoutSeconds) * 1000 ) diff --git a/packages/core/src/codewhisperer/service/testGenHandler.ts b/packages/core/src/codewhisperer/service/testGenHandler.ts new file mode 100644 index 00000000000..36f5b5b1d63 --- /dev/null +++ b/packages/core/src/codewhisperer/service/testGenHandler.ts @@ -0,0 +1,296 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ZipMetadata } from '../util/zipUtil' +import { getLogger } from '../../shared/logger' +import * as CodeWhispererConstants from '../models/constants' +import * as codewhispererClient from '../client/codewhisperer' +import * as codeWhisperer from '../client/codewhisperer' +import CodeWhispererUserClient, { + ArtifactMap, + CreateUploadUrlRequest, + TargetCode, +} from '../client/codewhispereruserclient' +import { CreateUploadUrlError, InvalidSourceZipError, TestGenFailedError, TestGenTimedOutError } from '../models/errors' +import { getMd5, uploadArtifactToS3 } from './securityScanHandler' +import { fs, randomUUID, sleep, tempDirPath } from '../../shared' +import { ShortAnswer, TestGenerationJobStatus, testGenState } from '..' +import { ChatSessionManager } from '../../amazonqTest/chat/storages/chatSession' +import { createCodeWhispererChatStreamingClient } from '../../shared/clients/codewhispererChatClient' +import { downloadExportResultArchive } from '../../shared/utilities/download' +import AdmZip from 'adm-zip' +import path from 'path' +import { ExportIntent } from '@amzn/codewhisperer-streaming' +import { glob } from 'glob' + +// TODO: Get TestFileName and Framework and to error message +export function throwIfCancelled() { + // TODO: fileName will be '' if user gives propt without opening + if (testGenState.isCancelling()) { + throw Error(CodeWhispererConstants.unitTestGenerationCancelMessage) + } +} + +export async function getPresignedUrlAndUploadTestGen(zipMetadata: ZipMetadata) { + const logger = getLogger() + if (zipMetadata.zipFilePath === '') { + getLogger().error('Failed to create valid source zip') + throw new InvalidSourceZipError() + } + const srcReq: CreateUploadUrlRequest = { + contentMd5: getMd5(zipMetadata.zipFilePath), + artifactType: 'SourceCode', + uploadIntent: CodeWhispererConstants.testGenUploadIntent, + } + logger.verbose(`Prepare for uploading src context...`) + const srcResp = await codeWhisperer.codeWhispererClient.createUploadUrl(srcReq).catch((err) => { + getLogger().error(`Failed getting presigned url for uploading src context. Request id: ${err.requestId}`) + throw new CreateUploadUrlError(err) + }) + logger.verbose(`CreateUploadUrlRequest requestId: ${srcResp.$response.requestId}`) + logger.verbose(`Complete Getting presigned Url for uploading src context.`) + logger.verbose(`Uploading src context...`) + await uploadArtifactToS3(zipMetadata.zipFilePath, srcResp) + logger.verbose(`Complete uploading src context.`) + const artifactMap: ArtifactMap = { + SourceCode: srcResp.uploadId, + } + return artifactMap +} + +export async function createTestJob( + artifactMap: codewhispererClient.ArtifactMap, + relativeTargetPath: TargetCode[], + userInputPrompt: string, + clientToken?: string +) { + const logger = getLogger() + logger.verbose(`Creating test job and starting startTestGeneration...`) + + // JS will minify this input object - fix that + const targetCodeList = relativeTargetPath.map((targetCode) => ({ + relativeTargetPath: targetCode.relativeTargetPath, + targetLineRangeList: targetCode.targetLineRangeList?.map((range) => ({ + start: { line: range.start.line, character: range.start.character }, + end: { line: range.end.line, character: range.end.character }, + })), + })) + logger.debug('updated target code list: %O', targetCodeList) + const req: CodeWhispererUserClient.StartTestGenerationRequest = { + uploadId: artifactMap.SourceCode, + targetCodeList, + userInput: userInputPrompt, + testGenerationJobGroupName: ChatSessionManager.Instance.getSession().testGenerationJobGroupName ?? randomUUID(), // TODO: remove fallback + clientToken, + } + logger.debug('Unit test generation request body: %O', req) + logger.debug('target code list: %O', req.targetCodeList[0]) + const firstTargetCodeList = req.targetCodeList?.[0] + const firstTargetLineRangeList = firstTargetCodeList?.targetLineRangeList?.[0] + logger.debug('target line range list: %O', firstTargetLineRangeList) + logger.debug('target line range start: %O', firstTargetLineRangeList?.start) + logger.debug('target line range end: %O', firstTargetLineRangeList?.end) + + const resp = await codewhispererClient.codeWhispererClient.startTestGeneration(req).catch((err) => { + ChatSessionManager.Instance.getSession().startTestGenerationRequestId = err.requestId + logger.error(`Failed creating test job. Request id: ${err.requestId}`) + throw err + }) + logger.info('Unit test generation request id: %s', resp.$response.requestId) + logger.debug('Unit test generation data: %O', resp.$response.data) + ChatSessionManager.Instance.getSession().startTestGenerationRequestId = resp.$response.requestId + if (resp.$response.error) { + logger.error('Unit test generation error: %O', resp.$response.error) + } + if (resp.testGenerationJob) { + ChatSessionManager.Instance.getSession().listOfTestGenerationJobId.push( + resp.testGenerationJob?.testGenerationJobId + ) + ChatSessionManager.Instance.getSession().testGenerationJobGroupName = + resp.testGenerationJob?.testGenerationJobGroupName + } + return resp +} + +export async function pollTestJobStatus( + jobId: string, + jobGroupName: string, + fileName: string, + initialExecution: boolean +) { + const session = ChatSessionManager.Instance.getSession() + const pollingStartTime = performance.now() + // We don't expect to get results immediately, so sleep for some time initially to not make unnecessary calls + await sleep(CodeWhispererConstants.testGenPollingDelaySeconds) + + const logger = getLogger() + logger.verbose(`Polling testgen job status...`) + let status = CodeWhispererConstants.TestGenerationJobStatus.IN_PROGRESS + while (true) { + throwIfCancelled() + const req: CodeWhispererUserClient.GetTestGenerationRequest = { + testGenerationJobId: jobId, + testGenerationJobGroupName: jobGroupName, + } + const resp = await codewhispererClient.codeWhispererClient.getTestGeneration(req) + logger.verbose('pollTestJobStatus request id: %s', resp.$response.requestId) + logger.debug('pollTestJobStatus testGenerationJob %O', resp.testGenerationJob) + ChatSessionManager.Instance.getSession().testGenerationJob = resp.testGenerationJob + const progressRate = resp.testGenerationJob?.progressRate ?? 0 + testGenState.getChatControllers()?.sendUpdatePromptProgress.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + status: 'InProgress', + progressRate, + }) + const shortAnswerString = resp.testGenerationJob?.shortAnswer + if (shortAnswerString) { + const parsedShortAnswer = JSON.parse(shortAnswerString) + const shortAnswer: ShortAnswer = JSON.parse(parsedShortAnswer) + // Stop the Unit test generation workflow if IDE receive stopIteration = true + if (shortAnswer.stopIteration === 'true') { + session.stopIteration = true + throw new TestGenFailedError(shortAnswer.planSummary) + } + if (shortAnswer.numberOfTestMethods) { + session.numberOfTestsGenerated = Number(shortAnswer.numberOfTestMethods) + } + if (shortAnswer.codeReferences) { + session.references = shortAnswer.codeReferences + } + if (initialExecution) { + session.generatedFilePath = shortAnswer?.testFilePath ?? '' + const currentPlanSummary = session.shortAnswer?.planSummary + const newPlanSummary = shortAnswer?.planSummary + const status = shortAnswer.stopIteration + + if (currentPlanSummary !== newPlanSummary && newPlanSummary) { + const chatControllers = testGenState.getChatControllers() + if (chatControllers) { + const currentSession = ChatSessionManager.Instance.getSession() + chatControllers.updateShortAnswer.fire({ + tabID: currentSession.tabID, + status, + shortAnswer, + testGenerationJobGroupName: resp.testGenerationJob?.testGenerationJobGroupName, + testGenerationJobId: resp.testGenerationJob?.testGenerationJobId, + fileName, + }) + } + } + } + ChatSessionManager.Instance.getSession().shortAnswer = shortAnswer + } + if (resp.testGenerationJob?.status !== TestGenerationJobStatus.IN_PROGRESS) { + // This can be FAILED or COMPLETED + status = resp.testGenerationJob?.status as TestGenerationJobStatus + logger.verbose(`testgen job status: ${status}`) + logger.verbose(`Complete polling test job status.`) + break + } + throwIfCancelled() + await sleep(CodeWhispererConstants.testGenJobPollingIntervalMilliseconds) + const elapsedTime = performance.now() - pollingStartTime + if (elapsedTime > CodeWhispererConstants.testGenJobTimeoutMilliseconds) { + logger.verbose(`testgen job status: ${status}`) + logger.verbose(`testgen job failed. Amazon Q timed out.`) + throw new TestGenTimedOutError() + } + } + return status +} + +/** + * Download the zip from exportResultsArchieve API and store in temp zip + */ +export async function exportResultsArchive( + uploadId: string, + groupName: string, + jobId: string, + projectName: string, + projectPath: string, + initialExecution: boolean +) { + // TODO: Make a common Temp folder + const pathToArchiveDir = path.join(tempDirPath, 'q-testgen') + + const archivePathExists = await fs.existsDir(pathToArchiveDir) + if (archivePathExists) { + await fs.delete(pathToArchiveDir, { recursive: true }) + } + await fs.mkdir(pathToArchiveDir) + + let downloadErrorMessage = undefined + try { + const pathToArchive = path.join(pathToArchiveDir, 'QTestGeneration.zip') + // Download and deserialize the zip + await downloadResultArchive(uploadId, groupName, jobId, pathToArchive) + const zip = new AdmZip(pathToArchive) + zip.extractAllTo(pathToArchiveDir, true) + + const session = ChatSessionManager.Instance.getSession() + const testFilePathFromResponse = session?.shortAnswer?.testFilePath + const testFilePath = testFilePathFromResponse + ? testFilePathFromResponse.split('/').slice(1).join('/') // remove the project name + : await getTestFilePathFromZip(pathToArchiveDir) + if (initialExecution) { + testGenState.getChatControllers()?.showCodeGenerationResults.fire({ + tabID: session.tabID, + filePath: testFilePath, + projectName, + }) + + // If User accepts the diff + testGenState.getChatControllers()?.sendUpdatePromptProgress.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + status: 'Completed', + }) + } + } catch (e) { + downloadErrorMessage = (e as Error).message + getLogger().error(`Unit Test Generation: ExportResultArchive error = ${downloadErrorMessage}`) + throw new Error('Error downloading test generation result artifacts: ' + downloadErrorMessage) + } +} + +async function getTestFilePathFromZip(pathToArchiveDir: string) { + const resultArtifactsDir = path.join(pathToArchiveDir, 'resultArtifacts') + const paths = await glob([resultArtifactsDir + '/**/*', '!**/.DS_Store'], { nodir: true }) + const absolutePath = paths[0] + const result = path.relative(resultArtifactsDir, absolutePath) + return result +} + +export async function downloadResultArchive( + uploadId: string, + testGenerationJobGroupName: string, + testGenerationJobId: string, + pathToArchive: string +) { + let downloadErrorMessage = undefined + const cwStreamingClient = await createCodeWhispererChatStreamingClient() + + try { + await downloadExportResultArchive( + cwStreamingClient, + { + exportId: uploadId, + exportIntent: ExportIntent.UNIT_TESTS, + exportContext: { + unitTestGenerationExportContext: { + testGenerationJobGroupName, + testGenerationJobId, + }, + }, + }, + pathToArchive + ) + } catch (e: any) { + downloadErrorMessage = (e as Error).message + getLogger().error(`Unit Test Generation: ExportResultArchive error = ${downloadErrorMessage}`) + throw e + } finally { + cwStreamingClient.destroy() + } +} diff --git a/packages/core/src/codewhisperer/service/transformByQ/humanInTheLoopManager.ts b/packages/core/src/codewhisperer/service/transformByQ/humanInTheLoopManager.ts index c31a1fd6365..0402483656d 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/humanInTheLoopManager.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/humanInTheLoopManager.ts @@ -64,7 +64,7 @@ export class HumanInTheLoopManager { } public getDependencyListXmlOutput = async () => - await fs.readFileAsString(path.join(this.tmpDependencyListDir, this.localPathToXmlDependencyList)) + await fs.readFileText(path.join(this.tmpDependencyListDir, this.localPathToXmlDependencyList)) public createPomFileCopy = async (outputDirectoryPath: string, pomFileVirtualFileReference: vscode.Uri) => { const newPomCopyRef = await createPomCopy(outputDirectoryPath, pomFileVirtualFileReference, 'pom.xml') diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index e83c3fa8c25..b6f3eda36ce 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ import * as vscode from 'vscode' -import * as fs from 'fs' +import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports import * as path from 'path' import * as os from 'os' import * as codeWhisperer from '../../client/codewhisperer' @@ -16,6 +16,7 @@ import { jobPlanProgress, sessionJobHistory, StepProgress, + TransformationType, transformByQState, TransformByQStatus, TransformByQStoppedError, @@ -33,20 +34,21 @@ import { import { sleep } from '../../../shared/utilities/timeoutUtils' import AdmZip from 'adm-zip' import globals from '../../../shared/extensionGlobals' -import { CredentialSourceId, telemetry } from '../../../shared/telemetry/telemetry' +import { telemetry } from '../../../shared/telemetry/telemetry' import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState' import { calculateTotalLatency } from '../../../amazonqGumby/telemetry/codeTransformTelemetry' import { MetadataResult } from '../../../shared/telemetry/telemetryClient' import request from '../../../shared/request' import { JobStoppedError, ZipExceedsSizeLimitError } from '../../../amazonqGumby/errors' import { writeLogs } from './transformFileHandler' -import { AuthUtil } from '../../util/authUtil' import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient' import { downloadExportResultArchive } from '../../../shared/utilities/download' import { ExportIntent, TransformationDownloadArtifactType } from '@amzn/codewhisperer-streaming' -import fs2 from '../../../shared/fs/fs' +import fs from '../../../shared/fs/fs' import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession' -import { convertToTimeString, encodeHTML } from '../../../shared/utilities/textUtilities' +import { encodeHTML } from '../../../shared/utilities/textUtilities' +import { convertToTimeString } from '../../../shared/datetime' +import { getAuthType } from '../../../auth/utils' export function getSha256(buffer: Buffer) { const hasher = crypto.createHash('sha256') @@ -54,16 +56,6 @@ export function getSha256(buffer: Buffer) { return hasher.digest('base64') } -export async function getAuthType() { - let authType: CredentialSourceId | undefined = undefined - if (AuthUtil.instance.isEnterpriseSsoInUse() && AuthUtil.instance.isConnectionValid()) { - authType = 'iamIdentityCenter' - } else if (AuthUtil.instance.isBuilderIdInUse() && AuthUtil.instance.isConnectionValid()) { - authType = 'awsId' - } - return authType -} - export function throwIfCancelled() { if (transformByQState.isCancelled()) { throw new TransformByQStoppedError() @@ -109,20 +101,47 @@ export async function uploadArtifactToS3( ) { throwIfCancelled() try { - const uploadFileByteSize = (await fs.promises.stat(fileName)).size + const uploadFileByteSize = (await nodefs.promises.stat(fileName)).size getLogger().info( - `Uploading project artifact at %s with checksum %s using uploadId: %s and size %s kB`, + `CodeTransformation: Uploading project artifact at %s with checksum %s using uploadId: %s and size %s kB`, fileName, sha256, resp.uploadId, Math.round(uploadFileByteSize / 1000) ) - const response = await request.fetch('PUT', resp.uploadUrl, { - body: buffer, - headers: getHeadersObj(sha256, resp.kmsKeyArn), - }).response - getLogger().info(`CodeTransformation: Status from S3 Upload = ${response.status}`) + let response = undefined + /* The existing S3 client has built-in retries but it requires the bucket name, so until + * CreateUploadUrl can be modified to return the S3 bucket name, manually implement retries. + * Alternatively, when waitUntil supports a fixed number of retries and retriableCodes, use that. + */ + const retriableCodes = [408, 429, 500, 502, 503, 504] + for (let i = 0; i < 4; i++) { + try { + response = await request.fetch('PUT', resp.uploadUrl, { + body: buffer, + headers: getHeadersObj(sha256, resp.kmsKeyArn), + }).response + getLogger().info(`CodeTransformation: upload to S3 status on attempt ${i + 1}/4 = ${response.status}`) + if (response.status === 200) { + break + } + throw new Error('Upload failed') + } catch (e: any) { + if (response && !retriableCodes.includes(response.status)) { + throw new Error(`Upload failed with status code = ${response.status}; did not automatically retry`) + } + if (i !== 3) { + await sleep(1000 * Math.pow(2, i)) + } + } + } + if (!response || response.status !== 200) { + const uploadFailedError = `Upload failed after up to 4 attempts with status code = ${response?.status ?? 'unavailable'}` + getLogger().error(`CodeTransformation: ${uploadFailedError}`) + throw new Error(uploadFailedError) + } + getLogger().info('CodeTransformation: Upload to S3 succeeded') } catch (e: any) { let errorMessage = `The upload failed due to: ${(e as Error).message}. For more information, see the [Amazon Q documentation](${CodeWhispererConstants.codeTransformTroubleshootUploadError})` if (errorMessage.includes('Request has expired')) { @@ -177,7 +196,7 @@ export async function stopJob(jobId: string) { } export async function uploadPayload(payloadFileName: string, uploadContext?: UploadContext) { - const buffer = fs.readFileSync(payloadFileName) + const buffer = Buffer.from(await fs.readFileBytes(payloadFileName)) const sha256 = getSha256(buffer) throwIfCancelled() @@ -212,6 +231,11 @@ export async function uploadPayload(payloadFileName: string, uploadContext?: Upl transformByQState.setJobId(encodeHTML(response.uploadId)) } jobPlanProgress['uploadCode'] = StepProgress.Succeeded + if (transformByQState.getTransformationType() === TransformationType.SQL_CONVERSION) { + // if doing a SQL conversion, we don't build the code or generate a plan, so mark these steps as succeeded immediately so that next step renders + jobPlanProgress['buildCode'] = StepProgress.Succeeded + jobPlanProgress['generatePlan'] = StepProgress.Succeeded + } updateJobHistory() return response.uploadId } @@ -225,6 +249,8 @@ export async function uploadPayload(payloadFileName: string, uploadContext?: Upl */ const mavenExcludedExtensions = ['.repositories', '.sha1'] +const sourceExcludedExtensions = ['.DS_Store'] + /** * Determines if the specified file path corresponds to a Maven metadata file * by checking against known metadata file extensions. This is used to identify @@ -237,24 +263,22 @@ function isExcludedDependencyFile(path: string): boolean { return mavenExcludedExtensions.some((extension) => path.endsWith(extension)) } -/** - * Gets all files in dir. We use this method to get the source code, then we run a mvn command to - * copy over dependencies into their own folder, then we use this method again to get those - * dependencies. If isDependenciesFolder is true, then we are getting all the files - * of the dependencies which were copied over by the previously-run mvn command, in which case - * we DO want to include any dependencies that may happen to be named "target", hence the check - * in the first part of the IF statement. The point of excluding folders named target is that - * "target" is also the name of the folder where .class files, large JARs, etc. are stored after - * building, and we do not want these included in the ZIP so we exclude these when calling - * getFilesRecursively on the source code folder. - */ -function getFilesRecursively(dir: string, isDependenciesFolder: boolean): string[] { - const entries = fs.readdirSync(dir, { withFileTypes: true }) +// do not zip the .DS_Store file as it may appear in the diff.patch +function isExcludedSourceFile(path: string): boolean { + return sourceExcludedExtensions.some((extension) => path.endsWith(extension)) +} + +// zip all dependency files and all source files excluding "target" (contains large JARs) plus ".git" and ".idea" (may appear in diff.patch) +export function getFilesRecursively(dir: string, isDependenciesFolder: boolean): string[] { + const entries = nodefs.readdirSync(dir, { withFileTypes: true }) const files = entries.flatMap((entry) => { const res = path.resolve(dir, entry.name) - // exclude 'target' directory from ZIP (except if zipping dependencies) due to issues in backend if (entry.isDirectory()) { - if (isDependenciesFolder || entry.name !== 'target') { + if (isDependenciesFolder) { + // include all dependency files + return getFilesRecursively(res, isDependenciesFolder) + } else if (entry.name !== 'target' && entry.name !== '.git' && entry.name !== '.idea') { + // exclude the above directories when zipping source code return getFilesRecursively(res, isDependenciesFolder) } else { return [] @@ -275,9 +299,9 @@ export function createZipManifest({ hilZipParams }: IZipManifestParams) { } interface IZipCodeParams { - dependenciesFolder: FolderInfo + dependenciesFolder?: FolderInfo humanInTheLoopFlag?: boolean - modulePath?: string + projectPath?: string zipManifest: ZipManifest | HilZipManifest } @@ -287,40 +311,69 @@ interface ZipCodeResult { fileSize: number } -export async function zipCode({ dependenciesFolder, humanInTheLoopFlag, modulePath, zipManifest }: IZipCodeParams) { +export async function zipCode( + { dependenciesFolder, humanInTheLoopFlag, projectPath, zipManifest }: IZipCodeParams, + zip: AdmZip = new AdmZip() +) { let tempFilePath = undefined let logFilePath = undefined let dependenciesCopied = false try { throwIfCancelled() - const zip = new AdmZip() - // If no modulePath is passed in, we are not uploaded the source folder - // NOTE: We only upload dependencies for human in the loop work - if (modulePath) { - const sourceFiles = getFilesRecursively(modulePath, false) + // if no project Path is passed in, we are not uploaded the source folder + // we only upload dependencies for human in the loop work + if (projectPath) { + const sourceFiles = getFilesRecursively(projectPath, false) let sourceFilesSize = 0 for (const file of sourceFiles) { - if (fs.statSync(file).isDirectory()) { - getLogger().info('CodeTransformation: Skipping directory, likely a symlink') + if (nodefs.statSync(file).isDirectory() || isExcludedSourceFile(file)) { + getLogger().info('CodeTransformation: Skipping file') continue } - const relativePath = path.relative(modulePath, file) + const relativePath = path.relative(projectPath, file) const paddedPath = path.join('sources', relativePath) zip.addLocalFile(file, path.dirname(paddedPath)) - sourceFilesSize += (await fs.promises.stat(file)).size + sourceFilesSize += (await nodefs.promises.stat(file)).size } getLogger().info(`CodeTransformation: source code files size = ${sourceFilesSize}`) } + if (transformByQState.getMultipleDiffs() && zipManifest instanceof ZipManifest) { + zipManifest.transformCapabilities.push('SELECTIVE_TRANSFORMATION_V1') + } + + if ( + transformByQState.getTransformationType() === TransformationType.SQL_CONVERSION && + zipManifest instanceof ZipManifest + ) { + // note that zipManifest must be a ZipManifest since only other option is HilZipManifest which is not used for SQL conversions + const metadataZip = new AdmZip(transformByQState.getMetadataPathSQL()) + zipManifest.requestedConversions = { + sqlConversion: { + source: transformByQState.getSourceDB(), + target: transformByQState.getTargetDB(), + schema: transformByQState.getSchema(), + host: transformByQState.getSourceServerName(), + sctFileName: metadataZip.getEntries().filter((entry) => entry.name.endsWith('.sct'))[0].name, + }, + } + // TO-DO: later consider making this add to path.join(zipManifest.dependenciesRoot, 'qct-sct-metadata', entry.entryName) so that it's more organized + metadataZip + .getEntries() + .forEach((entry) => zip.addFile(path.join(zipManifest.dependenciesRoot, entry.name), entry.getData())) + const sqlMetadataSize = (await nodefs.promises.stat(transformByQState.getMetadataPathSQL())).size + getLogger().info(`CodeTransformation: SQL metadata file size = ${sqlMetadataSize}`) + } + throwIfCancelled() let dependencyFiles: string[] = [] - if (fs.existsSync(dependenciesFolder.path)) { + if (dependenciesFolder && (await fs.exists(dependenciesFolder.path))) { dependencyFiles = getFilesRecursively(dependenciesFolder.path, true) } - if (dependencyFiles.length > 0) { + if (dependenciesFolder && dependencyFiles.length > 0) { let dependencyFilesSize = 0 for (const file of dependencyFiles) { if (isExcludedDependencyFile(file)) { @@ -330,14 +383,10 @@ export async function zipCode({ dependenciesFolder, humanInTheLoopFlag, modulePa // const paddedPath = path.join(`dependencies/${dependenciesFolder.name}`, relativePath) const paddedPath = path.join(`dependencies/`, relativePath) zip.addLocalFile(file, path.dirname(paddedPath)) - dependencyFilesSize += (await fs.promises.stat(file)).size + dependencyFilesSize += (await nodefs.promises.stat(file)).size } getLogger().info(`CodeTransformation: dependency files size = ${dependencyFilesSize}`) dependenciesCopied = true - } else { - if (zipManifest instanceof ZipManifest) { - zipManifest.dependenciesRoot = undefined - } } zip.addFile('manifest.json', Buffer.from(JSON.stringify(zipManifest)), 'utf-8') @@ -353,19 +402,20 @@ export async function zipCode({ dependenciesFolder, humanInTheLoopFlag, modulePa } tempFilePath = path.join(os.tmpdir(), 'zipped-code.zip') - fs.writeFileSync(tempFilePath, zip.toBuffer()) - if (fs.existsSync(dependenciesFolder.path)) { - fs.rmSync(dependenciesFolder.path, { recursive: true, force: true }) + await fs.writeFile(tempFilePath, zip.toBuffer()) + if (dependenciesFolder && (await fs.exists(dependenciesFolder.path))) { + await fs.delete(dependenciesFolder.path, { recursive: true, force: true }) } } catch (e: any) { + getLogger().error(`CodeTransformation: zipCode error = ${e}`) throw Error('Failed to zip project') } finally { if (logFilePath) { - fs.rmSync(logFilePath) + await fs.delete(logFilePath) } } - const zipSize = (await fs.promises.stat(tempFilePath)).size + const zipSize = (await nodefs.promises.stat(tempFilePath)).size const exceedsLimit = zipSize > CodeWhispererConstants.uploadZipSizeLimitInBytes @@ -392,9 +442,9 @@ export async function startJob(uploadId: string) { programmingLanguage: { languageName: CodeWhispererConstants.defaultLanguage.toLowerCase() }, }, transformationSpec: { - transformationType: CodeWhispererConstants.transformationType, - source: { language: sourceLanguageVersion }, - target: { language: targetLanguageVersion }, + transformationType: CodeWhispererConstants.transformationType, // shared b/w language upgrades & sql conversions for now + source: { language: sourceLanguageVersion }, // dummy value of JDK8 used for SQL conversions just so that this API can be called + target: { language: targetLanguageVersion }, // always JDK17 }, }) if (response.$response.requestId) { @@ -409,7 +459,7 @@ export async function startJob(uploadId: string) { } export function getImageAsBase64(filePath: string) { - const fileContents = fs.readFileSync(filePath, { encoding: 'base64' }) + const fileContents = nodefs.readFileSync(filePath, { encoding: 'base64' }) return `data:image/svg+xml;base64,${fileContents}` } @@ -542,6 +592,7 @@ export async function getTransformationPlan(jobId: string) { const linesOfCode = Number( jobStatistics.find((stat: { name: string; value: string }) => stat.name === 'linesOfCode').value ) + transformByQState.setLinesOfCodeSubmitted(linesOfCode) if (authType === 'iamIdentityCenter' && linesOfCode > CodeWhispererConstants.codeTransformLocThreshold) { plan += CodeWhispererConstants.codeTransformBillingText(linesOfCode) } @@ -593,7 +644,6 @@ export async function getTransformationSteps(jobId: string, handleThrottleFlag: export async function pollTransformationJob(jobId: string, validStates: string[]) { let status: string = '' - let timer: number = 0 while (true) { throwIfCancelled() try { @@ -646,10 +696,6 @@ export async function pollTransformationJob(jobId: string, validStates: string[] throw new JobStoppedError(response.$response.requestId) } await sleep(CodeWhispererConstants.transformationJobPollingIntervalSeconds * 1000) - timer += CodeWhispererConstants.transformationJobPollingIntervalSeconds - if (timer > CodeWhispererConstants.transformationJobTimeoutSeconds) { - throw new Error('Job timed out') - } } catch (e: any) { let errorMessage = (e as Error).message errorMessage += ` -- ${transformByQState.getJobFailureMetadata()}` @@ -725,9 +771,9 @@ export async function downloadAndExtractResultArchive( pathToArchiveDir: string, downloadArtifactType: TransformationDownloadArtifactType ) { - const archivePathExists = await fs2.existsDir(pathToArchiveDir) + const archivePathExists = await fs.existsDir(pathToArchiveDir) if (!archivePathExists) { - await fs2.mkdir(pathToArchiveDir) + await fs.mkdir(pathToArchiveDir) } const pathToArchive = path.join(pathToArchiveDir, 'ExportResultsArchive.zip') diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts index 87bca2498db..516471fc078 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts @@ -8,11 +8,15 @@ import * as path from 'path' import * as os from 'os' import xml2js = require('xml2js') import * as CodeWhispererConstants from '../../models/constants' -import { existsSync, writeFileSync } from 'fs' -import { BuildSystem, FolderInfo, transformByQState } from '../../models/model' +import { existsSync, readFileSync, writeFileSync } from 'fs' // eslint-disable-line no-restricted-imports +import { BuildSystem, DB, FolderInfo, transformByQState } from '../../models/model' import { IManifestFile } from '../../../amazonqFeatureDev/models' import fs from '../../../shared/fs/fs' import globals from '../../../shared/extensionGlobals' +import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession' +import { AbsolutePathDetectedError } from '../../../amazonqGumby/errors' +import { getLogger } from '../../../shared/logger' +import { isWin } from '../../../shared/vscode/env' export function getDependenciesFolderInfo(): FolderInfo { const dependencyFolderName = `${CodeWhispererConstants.dependencyFolderName}${globals.clock.Date.now()}` @@ -37,13 +41,111 @@ export async function checkBuildSystem(projectPath: string) { return BuildSystem.Unknown } +export async function parseBuildFile() { + try { + const absolutePaths = ['users/', 'system/', 'volumes/', 'c:\\', 'd:\\'] + const alias = path.basename(os.homedir()) + absolutePaths.push(alias) + const buildFilePath = path.join(transformByQState.getProjectPath(), 'pom.xml') + if (existsSync(buildFilePath)) { + const buildFileContents = readFileSync(buildFilePath).toString().toLowerCase() + const detectedPaths = [] + for (const absolutePath of absolutePaths) { + if (buildFileContents.includes(absolutePath)) { + detectedPaths.push(absolutePath) + } + } + if (detectedPaths.length > 0) { + const warningMessage = CodeWhispererConstants.absolutePathDetectedMessage( + detectedPaths.length, + path.basename(buildFilePath), + detectedPaths.join(', ') + ) + transformByQState.getChatControllers()?.errorThrown.fire({ + error: new AbsolutePathDetectedError(warningMessage), + tabID: ChatSessionManager.Instance.getSession().tabID, + }) + getLogger().info('CodeTransformation: absolute path potentially in build file') + return warningMessage + } + } + } catch (err: any) { + // swallow error + getLogger().error(`CodeTransformation: error scanning for absolute paths, tranformation continuing: ${err}`) + } + return undefined +} + +export async function validateSQLMetadataFile(fileContents: string, message: any) { + try { + const sctData = await xml2js.parseStringPromise(fileContents) + const dbEntities = sctData['tree']['instances'][0]['ProjectModel'][0]['entities'][0] + const sourceDB = dbEntities['sources'][0]['DbServer'][0]['$']['vendor'].trim().toUpperCase() + const targetDB = dbEntities['targets'][0]['DbServer'][0]['$']['vendor'].trim().toUpperCase() + const sourceServerName = dbEntities['sources'][0]['DbServer'][0]['$']['name'].trim() + transformByQState.setSourceServerName(sourceServerName) + if (sourceDB !== DB.ORACLE) { + transformByQState.getChatMessenger()?.sendUnrecoverableErrorResponse('unsupported-source-db', message.tabID) + return false + } else if (targetDB !== DB.AURORA_POSTGRESQL && targetDB !== DB.RDS_POSTGRESQL) { + transformByQState.getChatMessenger()?.sendUnrecoverableErrorResponse('unsupported-target-db', message.tabID) + return false + } + transformByQState.setSourceDB(sourceDB) + transformByQState.setTargetDB(targetDB) + + const serverNodeLocations = + sctData['tree']['instances'][0]['ProjectModel'][0]['relations'][0]['server-node-location'] + const schemaNames = new Set() + serverNodeLocations.forEach((serverNodeLocation: any) => { + const schemaNodes = serverNodeLocation['FullNameNodeInfoList'][0]['nameParts'][0][ + 'FullNameNodeInfo' + ].filter((node: any) => node['$']['typeNode'].toLowerCase() === 'schema') + schemaNodes.forEach((node: any) => { + schemaNames.add(node['$']['nameNode'].toUpperCase()) + }) + }) + transformByQState.setSchemaOptions(schemaNames) // user will choose one of these + getLogger().info( + `CodeTransformation: Parsed .sct file with source DB: ${sourceDB}, target DB: ${targetDB}, source host name: ${sourceServerName}, and schema names: ${Array.from(schemaNames)}` + ) + } catch (err: any) { + getLogger().error('CodeTransformation: Error parsing .sct file. %O', err) + transformByQState.getChatMessenger()?.sendUnrecoverableErrorResponse('error-parsing-sct-file', message.tabID) + return false + } + return true +} + +export async function setMaven() { + let mavenWrapperExecutableName = isWin() ? 'mvnw.cmd' : 'mvnw' + const mavenWrapperExecutablePath = path.join(transformByQState.getProjectPath(), mavenWrapperExecutableName) + if (existsSync(mavenWrapperExecutablePath)) { + if (mavenWrapperExecutableName === 'mvnw') { + mavenWrapperExecutableName = './mvnw' // add the './' for non-Windows + } else if (mavenWrapperExecutableName === 'mvnw.cmd') { + mavenWrapperExecutableName = '.\\mvnw.cmd' // add the '.\' for Windows + } + transformByQState.setMavenName(mavenWrapperExecutableName) + } else { + transformByQState.setMavenName('mvn') + } + getLogger().info(`CodeTransformation: using Maven ${transformByQState.getMavenName()}`) +} + +export async function openBuildLogFile() { + const logFilePath = transformByQState.getPreBuildLogFilePath() + const doc = await vscode.workspace.openTextDocument(logFilePath) + await vscode.window.showTextDocument(doc) +} + export async function createPomCopy( dirname: string, pomFileVirtualFileReference: vscode.Uri, fileName: string ): Promise { const newFilePath = path.join(dirname, fileName) - const pomFileContents = await fs.readFileAsString(pomFileVirtualFileReference.fsPath) + const pomFileContents = await fs.readFileText(pomFileVirtualFileReference.fsPath) const directoryExits = await fs.exists(dirname) if (!directoryExits) { await fs.mkdir(dirname) @@ -53,7 +155,7 @@ export async function createPomCopy( } export async function replacePomVersion(pomFileVirtualFileReference: vscode.Uri, version: string, delimiter: string) { - const pomFileText = await fs.readFileAsString(pomFileVirtualFileReference.fsPath) + const pomFileText = await fs.readFileText(pomFileVirtualFileReference.fsPath) const pomFileTextWithNewVersion = pomFileText.replace(delimiter, version) writeFileSync(pomFileVirtualFileReference.fsPath, pomFileTextWithNewVersion) } @@ -61,7 +163,7 @@ export async function replacePomVersion(pomFileVirtualFileReference: vscode.Uri, export async function getJsonValuesFromManifestFile( manifestFileVirtualFileReference: vscode.Uri ): Promise { - const manifestFileContents = await fs.readFileAsString(manifestFileVirtualFileReference.fsPath) + const manifestFileContents = await fs.readFileText(manifestFileVirtualFileReference.fsPath) const jsonValues = JSON.parse(manifestFileContents.toString()) return { hilCapability: jsonValues?.hilType, @@ -136,7 +238,7 @@ async function addDiagnosticOverview( export async function getCodeIssueSnippetFromPom(pomFileVirtualFileReference: vscode.Uri) { // TODO[gumby]: not great that we read this file multiple times - const pomFileContents = await fs.readFileAsString(pomFileVirtualFileReference.fsPath) + const pomFileContents = await fs.readFileText(pomFileVirtualFileReference.fsPath) const dependencyRegEx = /]*>(.*?)<\/dependencies>/ms const match = dependencyRegEx.exec(pomFileContents) diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts index 6e0583459d2..8ba8504e436 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts @@ -6,7 +6,8 @@ import * as vscode from 'vscode' import { FolderInfo, transformByQState } from '../../models/model' import { getLogger } from '../../../shared/logger' import * as CodeWhispererConstants from '../../models/constants' -import { spawnSync } from 'child_process' // Consider using ChildProcess once we finalize all spawnSync calls +// Consider using ChildProcess once we finalize all spawnSync calls +import { spawnSync } from 'child_process' // eslint-disable-line no-restricted-imports import { CodeTransformBuildCommand, telemetry } from '../../../shared/telemetry/telemetry' import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState' import { ToolkitError } from '../../../shared/errors' diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformProjectValidationHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformProjectValidationHandler.ts index c0f47e88d4c..ebc2caeda4c 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformProjectValidationHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformProjectValidationHandler.ts @@ -2,11 +2,8 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -import { BuildSystem, JDKVersion, TransformationCandidateProject } from '../../models/model' -import { getLogger } from '../../../shared/logger' -import * as CodeWhispererConstants from '../../models/constants' +import { BuildSystem, TransformationCandidateProject } from '../../models/model' import * as vscode from 'vscode' -import { spawnSync } from 'child_process' // Consider using ChildProcess once we finalize all spawnSync calls import { NoJavaProjectsFoundError, NoMavenJavaProjectsFoundError, @@ -17,7 +14,7 @@ import { checkBuildSystem } from './transformFileHandler' export async function getOpenProjects(): Promise { const folders = vscode.workspace.workspaceFolders - if (folders === undefined) { + if (folders === undefined || folders.length === 0) { throw new NoOpenProjectsError() } @@ -32,7 +29,7 @@ export async function getOpenProjects(): Promise 0) { - const classFilePath = `${compiledJavaFiles[0].fsPath}` - const baseCommand = 'javap' - const args = ['-v', classFilePath] - const spawnResult = spawnSync(baseCommand, args, { shell: false, encoding: 'utf-8' }) - if (spawnResult.status !== 0) { - let errorLog = '' - errorLog += spawnResult.error ? JSON.stringify(spawnResult.error) : '' - errorLog += `${spawnResult.stderr}\n${spawnResult.stdout}` - getLogger().error(`CodeTransformation: Error in running javap command = ${errorLog}`) - let errorReason = '' - if (spawnResult.stdout) { - errorReason = 'JavapExecutionError' - } else { - errorReason = 'JavapSpawnError' - } - if (spawnResult.error) { - const errorCode = (spawnResult.error as any).code ?? 'UNKNOWN' - errorReason += `-${errorCode}` - } - getLogger().error( - `CodeTransformation: Error in running javap command = ${errorReason}, log = ${errorLog}` - ) - } else { - const majorVersionIndex = spawnResult.stdout.indexOf('major version: ') - const javaVersion = spawnResult.stdout.slice(majorVersionIndex + 15, majorVersionIndex + 17).trim() - if (javaVersion === CodeWhispererConstants.JDK8VersionNumber) { - detectedJavaVersion = JDKVersion.JDK8 - } else if (javaVersion === CodeWhispererConstants.JDK11VersionNumber) { - detectedJavaVersion = JDKVersion.JDK11 - } else { - detectedJavaVersion = JDKVersion.UNSUPPORTED - } - } - } - - // detectedJavaVersion will be undefined if there are no .class files or if javap errors out, otherwise it will be JDK8, JDK11, or UNSUPPORTED - project.JDKVersion = detectedJavaVersion - projectsValidToTransform.push(project) - } - return projectsValidToTransform -} - -/* - * This function filters all open projects by first searching for a .java file and then searching for a pom.xml file in all projects. - * It also tries to detect the Java version of each project by running "javap" on a .class file of each project. - * As long as the project contains a .java file and a pom.xml file, the project is still considered valid for transformation, - * and we allow the user to specify the Java version. - */ -export async function validateOpenProjects( - projects: TransformationCandidateProject[], - onProjectFirstOpen: boolean = true -) { - const javaProjects = await getJavaProjects(projects) - - if (javaProjects.length === 0) { - throw new NoJavaProjectsFoundError() - } - - const mavenJavaProjects = await getMavenJavaProjects(javaProjects) if (mavenJavaProjects.length === 0) { - if (!onProjectFirstOpen) { - void vscode.window.showErrorMessage(CodeWhispererConstants.noPomXmlFoundNotification) - } throw new NoMavenJavaProjectsFoundError() } - // These projects we know must contain a pom.xml and a .java file - const projectsValidToTransform = await getProjectsValidToTransform(mavenJavaProjects, onProjectFirstOpen) + return mavenJavaProjects +} - return projectsValidToTransform +// This function filters all open projects by first searching for a .java file and then searching for a pom.xml file in all projects. +export async function validateOpenProjects(projects: TransformationCandidateProject[]) { + const javaProjects = await getJavaProjects(projects) + const mavenJavaProjects = await getMavenJavaProjects(javaProjects) + return mavenJavaProjects } diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts index b77f9d36668..98bdae3fcf4 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts @@ -6,8 +6,13 @@ import * as vscode from 'vscode' import globals from '../../../shared/extensionGlobals' import * as CodeWhispererConstants from '../../models/constants' -import { StepProgress, jobPlanProgress, sessionJobHistory, transformByQState } from '../../models/model' -import { convertToTimeString } from '../../../shared/utilities/textUtilities' +import { + StepProgress, + TransformationType, + jobPlanProgress, + sessionJobHistory, + transformByQState, +} from '../../models/model' import { getLogger } from '../../../shared/logger' import { getTransformationSteps } from './transformApiHandler' import { @@ -17,6 +22,7 @@ import { } from '../../../codewhisperer/client/codewhispereruserclient' import { startInterval } from '../../commands/startTransformByQ' import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState' +import { convertToTimeString } from '../../../shared/datetime' export class TransformationHubViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = 'aws.amazonq.transformationHub' @@ -263,13 +269,19 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider return CodeWhispererConstants.filesUploadedMessage case 'PREPARING': case 'PREPARED': - return CodeWhispererConstants.buildingCodeMessage.replace( - 'JAVA_VERSION_HERE', - transformByQState.getSourceJDKVersion() ?? '' - ) + // for SQL conversions, skip to transformingMessage since we don't build the code + return transformByQState.getTransformationType() === TransformationType.SQL_CONVERSION + ? CodeWhispererConstants.transformingMessage + : CodeWhispererConstants.buildingCodeMessage.replace( + 'JAVA_VERSION_HERE', + transformByQState.getSourceJDKVersion() ?? '' + ) case 'PLANNING': case 'PLANNED': - return CodeWhispererConstants.planningMessage + // for SQL conversions, skip to transformingMessage since we don't generate a plan + return transformByQState.getTransformationType() === TransformationType.SQL_CONVERSION + ? CodeWhispererConstants.transformingMessage + : CodeWhispererConstants.planningMessage case 'TRANSFORMING': case 'TRANSFORMED': case 'COMPLETED': @@ -307,7 +319,12 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider } let planSteps = transformByQState.getPlanSteps() - if (jobPlanProgress['generatePlan'] === StepProgress.Succeeded && transformByQState.isRunning()) { + // no plan for SQL conversions + if ( + transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION && + jobPlanProgress['generatePlan'] === StepProgress.Succeeded && + transformByQState.isRunning() + ) { planSteps = await getTransformationSteps(transformByQState.getJobId(), false) transformByQState.setPlanSteps(planSteps) } @@ -331,7 +348,7 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider activeStepId === 0 ) const buildMarkup = - activeStepId >= 1 + activeStepId >= 1 && transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION // for SQL conversions, don't show buildCode step ? simpleStep( this.getProgressIconMarkup(jobPlanProgress['buildCode']), CodeWhispererConstants.buildCodeStepMessage, @@ -339,7 +356,7 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider ) : '' const planMarkup = - activeStepId >= 2 + activeStepId >= 2 && transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION // for SQL conversions, don't show generatePlan step ? simpleStep( this.getProgressIconMarkup(jobPlanProgress['generatePlan']), CodeWhispererConstants.generatePlanStepMessage, @@ -358,9 +375,11 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider const isTransformFailed = jobPlanProgress['transformCode'] === StepProgress.Failed const progress = this.getTransformationStepProgressMarkup(planSteps, isTransformFailed) const latestGenericStepDetails = this.getLatestGenericStepDetails(transformByQState.getPolledJobStatus()) + const jobId = transformByQState.getJobId() progressHtml = `

Transformation Progress

+

${jobId ? `Job ID: ${jobId}` : ''}

${waitingMarkup} ${buildMarkup} ${planMarkup} diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index 2f31027fecc..1f4058a54dc 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -5,12 +5,18 @@ import AdmZip from 'adm-zip' import os from 'os' -import fs from 'fs' +import fs from 'fs' // eslint-disable-line no-restricted-imports import { parsePatch, applyPatches, ParsedDiff } from 'diff' import path from 'path' import vscode from 'vscode' import { ExportIntent } from '@amzn/codewhisperer-streaming' -import { TransformByQReviewStatus, transformByQState } from '../../models/model' +import { + TransformByQReviewStatus, + transformByQState, + PatchInfo, + DescriptionContent, + TransformationType, +} from '../../models/model' import { ExportResultArchiveStructure, downloadExportResultArchive } from '../../../shared/utilities/download' import { getLogger } from '../../../shared/logger' import { telemetry } from '../../../shared/telemetry/telemetry' @@ -20,6 +26,7 @@ import * as CodeWhispererConstants from '../../models/constants' import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient' import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession' import { setContext } from '../../../shared/vscode/setContext' +import * as codeWhisperer from '../../client/codewhisperer' export abstract class ProposedChangeNode { abstract readonly resourcePath: string @@ -32,7 +39,7 @@ export abstract class ProposedChangeNode { try { this.saveFile() } catch (err) { - //to do: file system-related error handling + // to do: file system-related error handling if (err instanceof Error) { getLogger().error(err.message) } @@ -106,6 +113,19 @@ export class AddedChangeNode extends ProposedChangeNode { } } +export class PatchFileNode { + label: string + readonly patchFilePath: string + children: ProposedChangeNode[] = [] + + constructor(description: PatchInfo | undefined = undefined, patchFilePath: string) { + this.patchFilePath = patchFilePath + this.label = description + ? `${description.name} (${description.isSuccessful ? 'Success' : 'Failure'})` + : path.basename(patchFilePath) + } +} + enum ReviewState { ToReview, Reviewed_Accepted, @@ -113,7 +133,8 @@ enum ReviewState { } export class DiffModel { - changes: ProposedChangeNode[] = [] + patchFileNodes: PatchFileNode[] = [] + currentPatchIndex: number = 0 /** * This function creates a copy of the changed files of the user's project so that the diff.patch can be applied to them @@ -142,8 +163,20 @@ export class DiffModel { * @param pathToWorkspace Path to the project that was transformed * @returns List of nodes containing the paths of files that were modified, added, or removed */ - public parseDiff(pathToDiff: string, pathToWorkspace: string): ProposedChangeNode[] { + public parseDiff( + pathToDiff: string, + pathToWorkspace: string, + diffDescription: PatchInfo | undefined, + totalDiffPatches: number + ): PatchFileNode { + this.patchFileNodes = [] const diffContents = fs.readFileSync(pathToDiff, 'utf8') + + if (!diffContents.trim()) { + getLogger().error(`CodeTransformation: diff.patch file is empty`) + throw new Error(CodeWhispererConstants.noChangesMadeMessage) + } + const changedFiles = parsePatch(diffContents) // path to the directory containing copy of the changed files in the transformed project const pathToTmpSrcDir = this.copyProject(pathToWorkspace, changedFiles) @@ -177,7 +210,9 @@ export class DiffModel { } }, }) - this.changes = changedFiles.flatMap((file) => { + const patchFileNode = new PatchFileNode(diffDescription, pathToDiff) + patchFileNode.label = `Patch ${this.currentPatchIndex + 1} of ${totalDiffPatches}: ${patchFileNode.label}` + patchFileNode.children = changedFiles.flatMap((file) => { /* ex. file.oldFileName = 'a/src/java/com/project/component/MyFile.java' * ex. file.newFileName = 'b/src/java/com/project/component/MyFile.java' * use substring(2) to ignore the 'a/' and 'b/' @@ -195,24 +230,24 @@ export class DiffModel { } return [] }) - - return this.changes + this.patchFileNodes.push(patchFileNode) + return patchFileNode } public getChanges() { - return this.changes + return this.patchFileNodes.flatMap((patchFileNode) => patchFileNode.children) } public getRoot() { - return this.changes[0] + return this.patchFileNodes.length > 0 ? this.patchFileNodes[0] : undefined } public saveChanges() { - this.changes.forEach((file) => { - file.saveChange() + this.patchFileNodes.forEach((patchFileNode) => { + patchFileNode.children.forEach((changeNode) => { + changeNode.saveChange() + }) }) - - this.clearChanges() } public rejectChanges() { @@ -220,11 +255,12 @@ export class DiffModel { } public clearChanges() { - this.changes = [] + this.patchFileNodes = [] + this.currentPatchIndex = 0 } } -export class TransformationResultsProvider implements vscode.TreeDataProvider { +export class TransformationResultsProvider implements vscode.TreeDataProvider { public static readonly viewType = 'aws.amazonq.transformationProposedChangesTree' private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter() @@ -236,26 +272,49 @@ export class TransformationResultsProvider implements vscode.TreeDataProvider { - return element ? Promise.resolve([]) : this.model.getChanges() + /* + Here we check if the element is a PatchFileNode instance. If it is, we return its + children array, which contains ProposedChangeNode instances. This ensures that when the user expands a + PatchFileNode (representing a diff.patch file), its children (proposed change nodes) are displayed as indented nodes under it. + */ + public getChildren( + element?: ProposedChangeNode | PatchFileNode + ): (ProposedChangeNode | PatchFileNode)[] | Thenable<(ProposedChangeNode | PatchFileNode)[]> { + if (!element) { + return this.model.patchFileNodes + } else if (element instanceof PatchFileNode) { + return element.children + } else { + return Promise.resolve([]) + } } - public getParent(element: ProposedChangeNode): ProposedChangeNode | undefined { + public getParent(element: ProposedChangeNode | PatchFileNode): PatchFileNode | undefined { + if (element instanceof ProposedChangeNode) { + const patchFileNode = this.model.patchFileNodes.find((p) => p.children.includes(element)) + return patchFileNode + } return undefined } } export class ProposedTransformationExplorer { - private changeViewer: vscode.TreeView + private changeViewer: vscode.TreeView public static TmpDir = os.tmpdir() @@ -266,6 +325,10 @@ export class ProposedTransformationExplorer { treeDataProvider: transformDataProvider, }) + let patchFiles: string[] = [] + let singlePatchFile: string = '' + let patchFilesDescriptions: DescriptionContent | undefined = undefined + const reset = async () => { await setContext('gumby.transformationProposalReviewInProgress', false) await setContext('gumby.reviewState', TransformByQReviewStatus.NotStarted) @@ -367,15 +430,53 @@ export class ProposedTransformationExplorer { let deserializeErrorMessage = undefined let pathContainingArchive = '' + patchFiles = [] // reset patchFiles if there was a previous transformation try { // Download and deserialize the zip pathContainingArchive = path.dirname(pathToArchive) const zip = new AdmZip(pathToArchive) zip.extractAllTo(pathContainingArchive) + const files = fs.readdirSync(path.join(pathContainingArchive, ExportResultArchiveStructure.PathToPatch)) + if (files.length === 1) { + singlePatchFile = path.join( + pathContainingArchive, + ExportResultArchiveStructure.PathToPatch, + files[0] + ) + } else { + const jsonFile = files.find((file) => file.endsWith('.json')) + if (!jsonFile) { + throw new Error('Expected JSON file not found') + } + const filePath = path.join( + pathContainingArchive, + ExportResultArchiveStructure.PathToPatch, + jsonFile + ) + const jsonData = fs.readFileSync(filePath, 'utf-8') + patchFilesDescriptions = JSON.parse(jsonData) + } + if (patchFilesDescriptions !== undefined) { + for (const patchInfo of patchFilesDescriptions.content) { + patchFiles.push( + path.join( + pathContainingArchive, + ExportResultArchiveStructure.PathToPatch, + patchInfo.filename + ) + ) + } + } else { + patchFiles.push(singlePatchFile) + } + // Because multiple patches are returned once the ZIP is downloaded, we want to show the first one to start diffModel.parseDiff( - path.join(pathContainingArchive, ExportResultArchiveStructure.PathToDiffPatch), - transformByQState.getProjectPath() + patchFiles[0], + transformByQState.getProjectPath(), + patchFilesDescriptions ? patchFilesDescriptions.content[0] : undefined, + patchFiles.length ) + await setContext('gumby.reviewState', TransformByQReviewStatus.InReview) transformDataProvider.refresh() transformByQState.setSummaryFilePath( @@ -402,17 +503,84 @@ export class ProposedTransformationExplorer { `${CodeWhispererConstants.errorDeserializingDiffNotification} ${deserializeErrorMessage}` ) } + + try { + const metricsPath = path.join(pathContainingArchive, ExportResultArchiveStructure.PathToMetrics) + const metricsData = JSON.parse(fs.readFileSync(metricsPath, 'utf8')) + + await codeWhisperer.codeWhispererClient.sendTelemetryEvent({ + telemetryEvent: { + transformEvent: { + jobId: transformByQState.getJobId(), + timestamp: new Date(), + ideCategory: 'VSCODE', + programmingLanguage: { + languageName: + transformByQState.getTransformationType() === TransformationType.LANGUAGE_UPGRADE + ? 'java' + : 'sql', + }, + linesOfCodeChanged: metricsData.linesOfCodeChanged, + charsOfCodeChanged: metricsData.charactersOfCodeChanged, + linesOfCodeSubmitted: transformByQState.getLinesOfCodeSubmitted(), // currently unavailable for SQL conversions + }, + }, + }) + } catch (err: any) { + // log error, but continue to show user diff.patch with results + getLogger().error(`CodeTransformation: SendTelemetryEvent error = ${err.message}`) + } }) vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.acceptChanges', async () => { diffModel.saveChanges() - telemetry.ui_click.emit({ elementId: 'transformationHub_acceptChanges' }) - void vscode.window.showInformationMessage(CodeWhispererConstants.changesAppliedNotification) + telemetry.codeTransform_submitSelection.emit({ + codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(), + codeTransformJobId: transformByQState.getJobId(), + userChoice: `acceptChanges-${patchFilesDescriptions?.content[diffModel.currentPatchIndex].name}`, + }) + if (transformByQState.getMultipleDiffs()) { + void vscode.window.showInformationMessage( + CodeWhispererConstants.changesAppliedNotificationMultipleDiffs( + diffModel.currentPatchIndex, + patchFiles.length + ) + ) + } else { + void vscode.window.showInformationMessage(CodeWhispererConstants.changesAppliedNotificationOneDiff) + } + + // We do this to ensure that the changesAppliedChatMessage is only sent to user when they accept the first diff.patch transformByQState.getChatControllers()?.transformationFinished.fire({ - message: CodeWhispererConstants.changesAppliedChatMessage, + message: CodeWhispererConstants.changesAppliedChatMessageMultipleDiffs( + diffModel.currentPatchIndex, + patchFiles.length, + patchFilesDescriptions + ? patchFilesDescriptions.content[diffModel.currentPatchIndex].name + : undefined + ), tabID: ChatSessionManager.Instance.getSession().tabID, + includeStartNewTransformationButton: diffModel.currentPatchIndex === patchFiles.length - 1, }) - await reset() + + // Load the next patch file + diffModel.currentPatchIndex++ + if (diffModel.currentPatchIndex < patchFiles.length) { + const nextPatchFile = patchFiles[diffModel.currentPatchIndex] + const nextPatchFileDescription = patchFilesDescriptions + ? patchFilesDescriptions.content[diffModel.currentPatchIndex] + : undefined + diffModel.parseDiff( + nextPatchFile, + transformByQState.getProjectPath(), + nextPatchFileDescription, + patchFiles.length + ) + transformDataProvider.refresh() + } else { + // All patches have been applied, reset the state + await reset() + } telemetry.codeTransform_viewArtifact.emit({ codeTransformArtifactType: 'ClientInstructions', @@ -428,7 +596,10 @@ export class ProposedTransformationExplorer { vscode.commands.registerCommand('aws.amazonq.transformationHub.reviewChanges.rejectChanges', async () => { diffModel.rejectChanges() await reset() - telemetry.ui_click.emit({ elementId: 'transformationHub_rejectChanges' }) + + transformByQState.getChatControllers()?.transformationFinished.fire({ + tabID: ChatSessionManager.Instance.getSession().tabID, + }) telemetry.codeTransform_viewArtifact.emit({ codeTransformArtifactType: 'ClientInstructions', diff --git a/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts b/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts index 633bf9839c1..925609ce185 100644 --- a/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts +++ b/packages/core/src/codewhisperer/tracker/codewhispererCodeCoverageTracker.ts @@ -8,15 +8,14 @@ import { getLogger } from '../../shared/logger/logger' import * as CodeWhispererConstants from '../models/constants' import globals from '../../shared/extensionGlobals' import { vsCodeState } from '../models/model' -import { distance } from 'fastest-levenshtein' import { CodewhispererLanguage, telemetry } from '../../shared/telemetry/telemetry' import { runtimeLanguageContext } from '../util/runtimeLanguageContext' import { TelemetryHelper } from '../util/telemetryHelper' import { AuthUtil } from '../util/authUtil' -import { CodeWhispererUserGroupSettings } from '../util/userGroupUtil' import { getSelectedCustomization } from '../util/customizationUtil' import { codeWhispererClient as client } from '../client/codewhisperer' import { isAwsError } from '../../shared/errors' +import { getUnmodifiedAcceptedTokens } from '../util/commonUtil' interface CodeWhispererToken { range: vscode.Range @@ -87,18 +86,10 @@ export class CodeWhispererCodeCoverageTracker { for (let i = 0; i < this._acceptedTokens[filename].length; i++) { const oldText = this._acceptedTokens[filename][i].text const newText = editor.document.getText(this._acceptedTokens[filename][i].range) - this._acceptedTokens[filename][i].accepted = this.getUnmodifiedAcceptedTokens(oldText, newText) + this._acceptedTokens[filename][i].accepted = getUnmodifiedAcceptedTokens(oldText, newText) } } } - // With edit distance, complicate usermodification can be considered as simple edit(add, delete, replace), - // and thus the unmodified part of recommendation length can be deducted/approximated - // ex. (modified > original): originalRecom: foo -> modifiedRecom: fobarbarbaro, distance = 9, delta = 12 - 9 = 3 - // ex. (modified == original): originalRecom: helloworld -> modifiedRecom: HelloWorld, distance = 2, delta = 10 - 2 = 8 - // ex. (modified < original): originalRecom: CodeWhisperer -> modifiedRecom: CODE, distance = 12, delta = 13 - 12 = 1 - public getUnmodifiedAcceptedTokens(origin: string, after: string) { - return Math.max(origin.length, after.length) - distance(origin, after) - } public emitCodeWhispererCodeContribution() { let totalTokens = 0 @@ -120,7 +111,7 @@ export class CodeWhispererCodeCoverageTracker { } }) } - const percentCount = ((unmodifiedAcceptedTokens / totalTokens) * 100).toFixed(2) + const percentCount = ((acceptedTokens / totalTokens) * 100).toFixed(2) const percentage = Math.round(parseInt(percentCount)) const selectedCustomization = getSelectedCustomization() if (this._serviceInvocationCount <= 0) { @@ -134,7 +125,6 @@ export class CodeWhispererCodeCoverageTracker { codewhispererSuggestedTokens: acceptedTokens, codewhispererPercentage: percentage ? percentage : 0, successCount: this._serviceInvocationCount, - codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(), codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, credentialStartUrl: AuthUtil.instance.startUrl, }) diff --git a/packages/core/src/codewhisperer/tracker/codewhispererTracker.ts b/packages/core/src/codewhisperer/tracker/codewhispererTracker.ts index e15c43e42ee..05a6d83f3f0 100644 --- a/packages/core/src/codewhisperer/tracker/codewhispererTracker.ts +++ b/packages/core/src/codewhisperer/tracker/codewhispererTracker.ts @@ -9,14 +9,14 @@ import { distance } from 'fastest-levenshtein' import { AcceptedSuggestionEntry } from '../models/model' import { getLogger } from '../../shared/logger/logger' import { AmazonqModifyCode, telemetry } from '../../shared/telemetry/telemetry' -import { CodeWhispererUserGroupSettings } from '../util/userGroupUtil' import { AuthUtil } from '../util/authUtil' import { InsertedCode } from '../../codewhispererChat/controllers/chat/model' import { codeWhispererClient } from '../client/codewhisperer' import { logSendTelemetryEventFailure } from '../../codewhispererChat/controllers/chat/telemetryHelper' import { Timeout } from '../../shared/utilities/timeoutUtils' import { getSelectedCustomization } from '../util/customizationUtil' -import { undefinedIfEmpty } from '../../shared' +import { isAwsError, undefinedIfEmpty } from '../../shared' +import { getUnmodifiedAcceptedTokens } from '../util/commonUtil' /** * This singleton class is mainly used for calculating the percentage of user modification. @@ -90,19 +90,20 @@ export class CodeWhispererTracker { public async emitTelemetryOnSuggestion(suggestion: AcceptedSuggestionEntry | InsertedCode) { let percentage = 1.0 + let currString = '' + const customizationArn = undefinedIfEmpty(getSelectedCustomization().arn) try { if (suggestion.fileUrl?.scheme !== '') { const document = await vscode.workspace.openTextDocument(suggestion.fileUrl) if (document) { - const currString = document.getText( - new vscode.Range(suggestion.startPosition, suggestion.endPosition) - ) + currString = document.getText(new vscode.Range(suggestion.startPosition, suggestion.endPosition)) percentage = this.checkDiff(currString, suggestion.originalString) } } } catch (e) { getLogger().verbose(`Exception Thrown from CodeWhispererTracker: ${e}`) + return } finally { if ('conversationID' in suggestion) { const event: AmazonqModifyCode = { @@ -121,7 +122,7 @@ export class CodeWhispererTracker { conversationId: event.cwsprChatConversationId, messageId: event.cwsprChatMessageId, modificationPercentage: event.cwsprChatModificationPercentage, - customizationArn: undefinedIfEmpty(getSelectedCustomization().arn), + customizationArn: customizationArn, }, }, }) @@ -137,13 +138,42 @@ export class CodeWhispererTracker { codewhispererCompletionType: suggestion.completionType, codewhispererLanguage: suggestion.language, credentialStartUrl: AuthUtil.instance.startUrl, - codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(), codewhispererCharactersAccepted: suggestion.originalString.length, codewhispererCharactersModified: 0, // TODO: currently we don't have an accurate number for this field with existing implementation }) - // TODO: - // Temperary comment out user modification event, need further discussion on how to calculate this metric - // TelemetryHelper.instance.sendUserModificationEvent(suggestion, percentage) + + codeWhispererClient + .sendTelemetryEvent({ + telemetryEvent: { + userModificationEvent: { + sessionId: suggestion.sessionId, + requestId: suggestion.requestId, + programmingLanguage: { languageName: suggestion.language }, + // deprecated % value and should not be used by service side + modificationPercentage: percentage, + customizationArn: customizationArn, + timestamp: new Date(), + acceptedCharacterCount: suggestion.originalString.length, + unmodifiedAcceptedCharacterCount: getUnmodifiedAcceptedTokens( + suggestion.originalString, + currString + ), + }, + }, + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + + getLogger().debug( + `Failed to send UserModificationEvent to CodeWhisperer, requestId: ${requestId ?? ''}, message: ${ + error.message + }` + ) + }) } } } diff --git a/packages/core/src/codewhisperer/ui/codeWhispererNodes.ts b/packages/core/src/codewhisperer/ui/codeWhispererNodes.ts index e256195cbef..5d8382cbec9 100644 --- a/packages/core/src/codewhisperer/ui/codeWhispererNodes.ts +++ b/packages/core/src/codewhisperer/ui/codeWhispererNodes.ts @@ -11,7 +11,6 @@ import { Commands, placeholder } from '../../shared/vscode/commands2' import { toggleCodeSuggestions, showReferenceLog, - showSecurityScan, showLearnMore, showFreeTierLimit, reconnect, @@ -44,9 +43,9 @@ export function createAutoSuggestions(running: boolean): DataQuickPickItem<'auto } export function createAutoScans(running: boolean): DataQuickPickItem<'autoScans'> { - const labelResume = localize('AWS.codewhisperer.resumeCodeWhispererNode.label', 'Resume Auto-Scans') + const labelResume = localize('AWS.codewhisperer.resumeCodeWhispererNode.label', 'Resume Auto-Reviews') const iconResume = getIcon('vscode-debug-alt') - const labelPause = localize('AWS.codewhisperer.pauseCodeWhispererNode.label', 'Pause Auto-Scans') + const labelPause = localize('AWS.codewhisperer.pauseCodeWhispererNode.label', 'Pause Auto-Reviews') const iconPause = getIcon('vscode-debug-pause') const monthlyQuotaExceeded = CodeScansState.instance.isMonthlyQuotaExceeded() @@ -70,14 +69,21 @@ export function createOpenReferenceLog(): DataQuickPickItem<'openReferenceLog'> } export function createSecurityScan(): DataQuickPickItem<'securityScan'> { - const prefix = codeScanState.getPrefixTextForButton() - const label = `${prefix} Project Scan` + const label = `Full project scan is now /review!` const icon = codeScanState.getIconForButton() + const description = 'Open in Chat Panel' return { data: 'securityScan', label: codicon`${icon} ${label}`, - onClick: () => showSecurityScan.execute(placeholder, cwQuickPickSource), + description: description, + onClick: () => + vscode.commands.executeCommand( + 'aws.amazonq.security.scan-statusbar', + placeholder, + 'cwQuickPickSource', + true + ), } as DataQuickPickItem<'securityScan'> } diff --git a/packages/core/src/codewhisperer/ui/statusBarMenu.ts b/packages/core/src/codewhisperer/ui/statusBarMenu.ts index 7a44a6361e1..8988f0eb339 100644 --- a/packages/core/src/codewhisperer/ui/statusBarMenu.ts +++ b/packages/core/src/codewhisperer/ui/statusBarMenu.ts @@ -6,7 +6,6 @@ import { createAutoSuggestions, createOpenReferenceLog, - createSecurityScan, createLearnMore, createFreeTierLimitMet, createSelectCustomization, @@ -21,6 +20,7 @@ import { createAutoScans, createSignIn, switchToAmazonQNode, + createSecurityScan, } from './codeWhispererNodes' import { hasVendedIamCredentials } from '../../auth/auth' import { AuthUtil } from '../util/authUtil' @@ -66,7 +66,7 @@ function getAmazonQCodeWhispererNodes() { createGettingStarted(), // "Learn" node : opens Learn CodeWhisperer page // Security scans - createSeparator('Security Scans'), + createSeparator('Code Reviews'), ...(AuthUtil.instance.isBuilderIdInUse() ? [] : [createAutoScans(autoScansEnabled)]), createSecurityScan(), diff --git a/packages/core/src/codewhisperer/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts index c4d2c0109b8..afb15aff133 100644 --- a/packages/core/src/codewhisperer/util/authUtil.ts +++ b/packages/core/src/codewhisperer/util/authUtil.ts @@ -59,11 +59,8 @@ export const isValidCodeWhispererCoreConnection = (conn?: Connection): conn is C return isIamConnection(conn) } - if (isSageMaker()) { - return isIamConnection(conn) - } - return ( + (isSageMaker() && isIamConnection(conn)) || (isCloud9('codecatalyst') && isIamConnection(conn)) || (isSsoConnection(conn) && hasScopes(conn, codeWhispererCoreScopes)) ) @@ -71,9 +68,10 @@ export const isValidCodeWhispererCoreConnection = (conn?: Connection): conn is C /** Superset that includes all of CodeWhisperer + Amazon Q */ export const isValidAmazonQConnection = (conn?: Connection): conn is Connection => { return ( - (isSsoConnection(conn) || isBuilderIdConnection(conn)) && - isValidCodeWhispererCoreConnection(conn) && - hasScopes(conn, amazonQScopes) + (isSageMaker() && isIamConnection(conn)) || + ((isSsoConnection(conn) || isBuilderIdConnection(conn)) && + isValidCodeWhispererCoreConnection(conn) && + hasScopes(conn, amazonQScopes)) ) } @@ -368,7 +366,7 @@ export class AuthUtil { public async notifySessionConfiguration() { const suppressId = 'amazonQSessionConfigurationMessage' const settings = AmazonQPromptSettings.instance - const shouldShow = await settings.isPromptEnabled(suppressId) + const shouldShow = settings.isPromptEnabled(suppressId) if (!shouldShow) { return } @@ -442,7 +440,8 @@ export class AuthUtil { if (conn === undefined) { return buildFeatureAuthState(AuthStates.disconnected) } - if (!isSsoConnection(conn)) { + + if (!isSsoConnection(conn) && !isSageMaker()) { throw new ToolkitError(`Connection "${conn.id}" is not a valid type: ${conn.type}`) } @@ -453,7 +452,7 @@ export class AuthUtil { return state } - if (isBuilderIdConnection(conn) || isIdcSsoConnection(conn)) { + if (isBuilderIdConnection(conn) || isIdcSsoConnection(conn) || isSageMaker()) { if (isValidCodeWhispererCoreConnection(conn)) { state[Features.codewhispererCore] = AuthStates.connected } diff --git a/packages/core/src/codewhisperer/util/codeWhispererSession.ts b/packages/core/src/codewhisperer/util/codeWhispererSession.ts index d6c06ef5350..e5daae22d17 100644 --- a/packages/core/src/codewhisperer/util/codeWhispererSession.ts +++ b/packages/core/src/codewhisperer/util/codeWhispererSession.ts @@ -12,7 +12,7 @@ import { } from '../../shared/telemetry/telemetry.gen' import { GenerateRecommendationsRequest, ListRecommendationsRequest, Recommendation } from '../client/codewhisperer' import { Position } from 'vscode' -import { CodeWhispererSupplementalContext } from '../models/model' +import { CodeWhispererSupplementalContext, vsCodeState } from '../models/model' class CodeWhispererSession { static #instance: CodeWhispererSession @@ -41,6 +41,9 @@ class CodeWhispererSession { fetchCredentialStartTime = 0 sdkApiCallStartTime = 0 invokeSuggestionStartTime = 0 + timeToFirstRecommendation = 0 + firstSuggestionShowTime = 0 + perceivedLatency = 0 public static get instance() { return (this.#instance ??= new CodeWhispererSession()) @@ -58,6 +61,12 @@ class CodeWhispererSession { } } + setTimeToFirstRecommendation(timeToFirstRecommendation: number) { + if (this.invokeSuggestionStartTime) { + this.timeToFirstRecommendation = timeToFirstRecommendation - this.invokeSuggestionStartTime + } + } + setSuggestionState(index: number, value: string) { this.suggestionStates.set(index, value) } @@ -75,6 +84,25 @@ class CodeWhispererSession { return this.completionTypes.get(index) || 'Line' } + getPerceivedLatency(triggerType: CodewhispererTriggerType) { + if (triggerType === 'OnDemand') { + return this.timeToFirstRecommendation + } else { + return session.firstSuggestionShowTime - vsCodeState.lastUserModificationTime + } + } + + setPerceivedLatency() { + if (this.perceivedLatency !== 0) { + return + } + if (this.triggerType === 'OnDemand') { + this.perceivedLatency = this.timeToFirstRecommendation + } else { + this.perceivedLatency = this.firstSuggestionShowTime - vsCodeState.lastUserModificationTime + } + } + reset() { this.sessionId = '' this.requestContext = { request: {} as any, supplementalMetadata: {} as any } diff --git a/packages/core/src/codewhisperer/util/codewhispererSettings.ts b/packages/core/src/codewhisperer/util/codewhispererSettings.ts index 09c7e2657bd..32ae9cd0307 100644 --- a/packages/core/src/codewhisperer/util/codewhispererSettings.ts +++ b/packages/core/src/codewhisperer/util/codewhispererSettings.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ import { fromExtensionManifest, migrateSetting } from '../../shared/settings' +import { ArrayConstructor } from '../../shared/utilities/typeConstructors' const description = { showInlineCodeSuggestionsWithCodeReferences: Boolean, // eslint-disable-line id-length @@ -12,6 +13,7 @@ const description = { workspaceIndexWorkerThreads: Number, workspaceIndexUseGPU: Boolean, workspaceIndexMaxSize: Number, + ignoredSecurityIssues: ArrayConstructor(String), } export class CodeWhispererSettings extends fromExtensionManifest('amazonQ', description) { @@ -64,6 +66,14 @@ export class CodeWhispererSettings extends fromExtensionManifest('amazonQ', desc return Math.max(this.get('workspaceIndexMaxSize', 250), 1) } + public getIgnoredSecurityIssues(): string[] { + return this.get('ignoredSecurityIssues', []) + } + + public async addToIgnoredSecurityIssuesList(issueTitle: string) { + await this.update('ignoredSecurityIssues', [...this.getIgnoredSecurityIssues(), issueTitle]) + } + static #instance: CodeWhispererSettings public static get instance() { diff --git a/packages/core/src/codewhisperer/util/commonUtil.ts b/packages/core/src/codewhisperer/util/commonUtil.ts index 05fe21458a7..1d624e77b5e 100644 --- a/packages/core/src/codewhisperer/util/commonUtil.ts +++ b/packages/core/src/codewhisperer/util/commonUtil.ts @@ -5,9 +5,14 @@ import * as vscode from 'vscode' import * as semver from 'semver' +import { distance } from 'fastest-levenshtein' import { isCloud9 } from '../../shared/extensionUtilities' import { getInlineSuggestEnabled } from '../../shared/utilities/editorUtilities' -import { AWSTemplateCaseInsensitiveKeyWords, AWSTemplateKeyWords } from '../models/constants' +import { + AWSTemplateCaseInsensitiveKeyWords, + AWSTemplateKeyWords, + JsonConfigFileNamingConvention, +} from '../models/constants' export function getLocalDatetime() { const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone @@ -61,13 +66,23 @@ export function getPrefixSuffixOverlap(firstString: string, secondString: string return secondString.slice(0, i) } -export function checkLeftContextKeywordsForJsonAndYaml(leftFileContent: string, language: string): boolean { +export function checkLeftContextKeywordsForJson(fileName: string, leftFileContent: string, language: string): boolean { if ( - (language === 'json' || language === 'yaml') && + language === 'json' && !AWSTemplateKeyWords.some((substring) => leftFileContent.includes(substring)) && - !AWSTemplateCaseInsensitiveKeyWords.some((substring) => leftFileContent.toLowerCase().includes(substring)) + !AWSTemplateCaseInsensitiveKeyWords.some((substring) => leftFileContent.toLowerCase().includes(substring)) && + !JsonConfigFileNamingConvention.has(fileName.toLowerCase()) ) { return true } return false } + +// With edit distance, complicate usermodification can be considered as simple edit(add, delete, replace), +// and thus the unmodified part of recommendation length can be deducted/approximated +// ex. (modified > original): originalRecom: foo -> modifiedRecom: fobarbarbaro, distance = 9, delta = 12 - 9 = 3 +// ex. (modified == original): originalRecom: helloworld -> modifiedRecom: HelloWorld, distance = 2, delta = 10 - 2 = 8 +// ex. (modified < original): originalRecom: CodeWhisperer -> modifiedRecom: CODE, distance = 12, delta = 13 - 12 = 1 +export function getUnmodifiedAcceptedTokens(origin: string, after: string) { + return Math.max(origin.length, after.length) - distance(origin, after) +} diff --git a/packages/core/src/codewhisperer/util/customizationUtil.ts b/packages/core/src/codewhisperer/util/customizationUtil.ts index de4880a737c..cfaf68b1afd 100644 --- a/packages/core/src/codewhisperer/util/customizationUtil.ts +++ b/packages/core/src/codewhisperer/util/customizationUtil.ts @@ -18,7 +18,7 @@ import { showMessageWithUrl } from '../../shared/utilities/messages' import { parse } from '@aws-sdk/util-arn-parser' import { Commands } from '../../shared/vscode/commands2' import { vsCodeState } from '../models/model' -import { FeatureConfigProvider } from '../../shared/featureConfig' +import { FeatureConfigProvider, Features } from '../../shared/featureConfig' /** * @@ -108,7 +108,9 @@ export const getSelectedCustomization = (): Customization => { const result = selectedCustomizationArr[AuthUtil.instance.conn.label] || baseCustomization // A/B case - const arnOverride = FeatureConfigProvider.instance.getCustomizationArnOverride() + const customizationFeature = FeatureConfigProvider.getFeature(Features.customizationArnOverride) + const arnOverride = customizationFeature?.value.stringValue + const customizationOverrideName = customizationFeature?.variation if (arnOverride === undefined || arnOverride === '') { return result } else { @@ -116,7 +118,7 @@ export const getSelectedCustomization = (): Customization => { // but still shows customization info of user's currently selected. return { arn: arnOverride, - name: result.name, + name: customizationOverrideName, description: result.description, } } diff --git a/packages/core/src/codewhisperer/util/editorContext.ts b/packages/core/src/codewhisperer/util/editorContext.ts index e81565282f9..4b58cb4f848 100644 --- a/packages/core/src/codewhisperer/util/editorContext.ts +++ b/packages/core/src/codewhisperer/util/editorContext.ts @@ -14,9 +14,10 @@ import { fetchSupplementalContext } from './supplementalContext/supplementalCont import { supplementalContextTimeoutInMs } from '../models/constants' import { getSelectedCustomization } from './customizationUtil' import { selectFrom } from '../../shared/utilities/tsUtils' -import { checkLeftContextKeywordsForJsonAndYaml } from './commonUtil' +import { checkLeftContextKeywordsForJson } from './commonUtil' import { CodeWhispererSupplementalContext } from '../models/model' import { getOptOutPreference } from '../../shared/telemetry/util' +import { indent } from '../../shared' let tabSize: number = getTabSizeSetting() @@ -39,7 +40,7 @@ export function extractContextForCodeWhisperer(editor: vscode.TextEditor): codew ) ) let languageName = 'plaintext' - if (!checkLeftContextKeywordsForJsonAndYaml(caretLeftFileContext, editor.document.languageId)) { + if (!checkLeftContextKeywordsForJson(document.fileName, caretLeftFileContext, editor.document.languageId)) { languageName = runtimeLanguageContext.normalizeLanguage(editor.document.languageId) ?? editor.document.languageId } @@ -129,9 +130,11 @@ export async function buildGenerateRecommendationRequest(editor: vscode.TextEdit const fileContext = extractContextForCodeWhisperer(editor) const tokenSource = new vscode.CancellationTokenSource() + // the supplement context fetch mechanisms each has a timeout of supplementalContextTimeoutInMs + // adding 10 ms for overall timeout as buffer setTimeout(() => { tokenSource.cancel() - }, supplementalContextTimeoutInMs) + }, supplementalContextTimeoutInMs + 10) const supplementalContexts = await fetchSupplementalContext(editor, tokenSource.token) logSupplementalContext(supplementalContexts) @@ -202,18 +205,26 @@ function logSupplementalContext(supplementalContext: CodeWhispererSupplementalCo return } - let logString = `CodeWhispererSupplementalContext: - isUtg: ${supplementalContext.isUtg}, - isProcessTimeout: ${supplementalContext.isProcessTimeout}, - contentsLength: ${supplementalContext.contentsLength}, - latency: ${supplementalContext.latency}, -` + let logString = indent( + `CodeWhispererSupplementalContext: + isUtg: ${supplementalContext.isUtg}, + isProcessTimeout: ${supplementalContext.isProcessTimeout}, + contentsLength: ${supplementalContext.contentsLength}, + latency: ${supplementalContext.latency} + strategy: ${supplementalContext.strategy}`, + 4, + true + ).trimStart() + supplementalContext.supplementalContextItems.forEach((context, index) => { - logString += `Chunk ${index}: - Path: ${context.filePath} - Content: ${index}:${context.content} - Score: ${context.score} - -----------------------------------------------` + logString += indent(`\nChunk ${index}:\n`, 4, true) + logString += indent( + `Path: ${context.filePath} + Length: ${context.content.length} + Score: ${context.score}`, + 8, + true + ) }) getLogger().debug(logString) diff --git a/packages/core/src/codewhisperer/util/gitUtil.ts b/packages/core/src/codewhisperer/util/gitUtil.ts new file mode 100644 index 00000000000..752c16ba6bf --- /dev/null +++ b/packages/core/src/codewhisperer/util/gitUtil.ts @@ -0,0 +1,36 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getLogger, removeAnsi } from '../../shared' +import { ChildProcess, ChildProcessOptions } from '../../shared/utilities/processUtils' +import { Uri } from 'vscode' + +export async function isGitRepo(folder: Uri): Promise { + const childProcess = new ChildProcess('git', ['rev-parse', '--is-inside-work-tree']) + + let output = '' + const runOptions: ChildProcessOptions = { + rejectOnError: true, + rejectOnErrorCode: true, + onStdout: (text) => { + output += text + getLogger().verbose(removeAnsi(text)) + }, + onStderr: (text) => { + getLogger().error(removeAnsi(text)) + }, + spawnOptions: { + cwd: folder.fsPath, + }, + } + + try { + await childProcess.run(runOptions) + return output.trim() === 'true' + } catch (err) { + getLogger().warn(`Failed to run command \`${childProcess.toString()}\`: ${err}`) + return false + } +} diff --git a/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts b/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts index 646581b83d1..9a495cf5356 100644 --- a/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts +++ b/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts @@ -3,13 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ +import * as vscode from 'vscode' import { getLogger } from '../../shared/logger/logger' import { CodewhispererLanguage } from '../../shared/telemetry/telemetry.gen' import { createConstantMap, ConstantMap } from '../../shared/utilities/tsUtils' import * as codewhispererClient from '../client/codewhisperer' import * as CodeWhispererConstants from '../models/constants' +import * as path from 'path' -type RuntimeLanguage = Exclude +type RuntimeLanguage = Exclude | 'systemverilog' const runtimeLanguageSet: ReadonlySet = new Set([ 'c', @@ -21,15 +23,22 @@ const runtimeLanguageSet: ReadonlySet = new Set([ 'kotlin', 'php', 'python', + 'powershell', + 'r', + 'dart', 'ruby', 'rust', 'scala', 'shell', 'sql', + 'swift', + 'lua', + 'vue', 'typescript', 'json', 'yaml', 'tf', + 'systemverilog', ]) export class RuntimeLanguageContext { @@ -93,11 +102,16 @@ export class RuntimeLanguageContext { r: 'r', swift: 'swift', systemVerilog: 'systemVerilog', + systemverilog: 'systemVerilog', + verilog: 'systemVerilog', vue: 'vue', }) this.supportedLanguageExtensionMap = createConstantMap({ c: 'c', + h: 'c', cpp: 'cpp', + cc: 'cpp', + 'c++': 'cpp', cs: 'csharp', go: 'go', hcl: 'tf', @@ -120,6 +134,17 @@ export class RuntimeLanguageContext { ts: 'typescript', yaml: 'yaml', yml: 'yaml', + sv: 'systemVerilog', + svh: 'systemVerilog', + vh: 'systemVerilog', + dart: 'dart', + lua: 'lua', + wlua: 'lua', + swift: 'swift', + vue: 'vue', + ps1: 'powershell', + psm1: 'powershell', + r: 'r', }) } @@ -147,6 +172,9 @@ export class RuntimeLanguageContext { case 'tsx': return 'typescript' + case 'systemVerilog': + return 'systemverilog' + default: if (!runtimeLanguageSet.has(language)) { getLogger().error(`codewhisperer: unknown runtime language ${language}`) @@ -228,33 +256,24 @@ export class RuntimeLanguageContext { } } - /** - * - * @param languageId: either vscodeLanguageId or CodewhispererLanguage - * @returns true if the language is supported by CodeWhisperer otherwise false - */ - public isLanguageSupported(languageId: string): boolean { - const lang = this.normalizeLanguage(languageId) - switch (lang) { - case undefined: - return false - - case 'plaintext': - return false + public isLanguageSupported(languageId: string): boolean + public isLanguageSupported(doc: vscode.TextDocument): boolean + public isLanguageSupported(arg: string | vscode.TextDocument): boolean { + if (typeof arg === 'string') { + const normalizedLanguageId = this.normalizeLanguage(arg) + const byLanguageId = !normalizedLanguageId || normalizedLanguageId === 'plaintext' ? false : true - case 'dart': - case 'lua': - case 'powershell': - case 'r': - case 'swift': - case 'vue': - case 'systemVerilog': - return false + return byLanguageId + } else { + const normalizedLanguageId = this.normalizeLanguage(arg.languageId) + const byLanguageId = !normalizedLanguageId || normalizedLanguageId === 'plaintext' ? false : true + const extension = path.extname(arg.uri.fsPath) + const byFileExtension = this.isFileFormatSupported(extension.substring(1)) - default: - return true + return byLanguageId || byFileExtension } } + /** * * @param fileFormat : vscode editor filecontext filename extension diff --git a/packages/core/src/codewhisperer/util/securityScanLanguageContext.ts b/packages/core/src/codewhisperer/util/securityScanLanguageContext.ts index 466a558a2eb..1966bbac402 100644 --- a/packages/core/src/codewhisperer/util/securityScanLanguageContext.ts +++ b/packages/core/src/codewhisperer/util/securityScanLanguageContext.ts @@ -17,7 +17,9 @@ export class SecurityScanLanguageContext { java: 'java', python: 'python', javascript: 'javascript', + javascriptreact: 'javascript', typescript: 'typescript', + typescriptreact: 'typescript', csharp: 'csharp', go: 'go', golang: 'go', @@ -40,6 +42,11 @@ export class SecurityScanLanguageContext { 'java-properties': 'plaintext', 'go.mod': 'plaintext', 'go.sum': 'plaintext', + kotlin: 'kotlin', + scala: 'scala', + sh: 'shell', + shell: 'shell', + shellscript: 'shell', }) } diff --git a/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts b/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts index 2af5dcb79cd..a4b8aa5fba1 100644 --- a/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts +++ b/packages/core/src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts @@ -4,18 +4,21 @@ */ import * as vscode from 'vscode' -import { fs } from '../../../shared' +import { FeatureConfigProvider, fs } from '../../../shared' import path = require('path') import { BM25Document, BM25Okapi } from './rankBm25' -import { ToolkitError } from '../../../shared/errors' -import { UserGroup, crossFileContextConfig, supplemetalContextFetchingTimeoutMsg } from '../../models/constants' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { CodeWhispererUserGroupSettings } from '../userGroupUtil' +import { crossFileContextConfig, supplementalContextTimeoutInMs } from '../../models/constants' import { isTestFile } from './codeParsingUtil' import { getFileDistance } from '../../../shared/filesystemUtilities' import { getOpenFilesInWindow } from '../../../shared/utilities/editorUtilities' import { getLogger } from '../../../shared/logger/logger' -import { CodeWhispererSupplementalContext, CodeWhispererSupplementalContextItem } from '../../models/model' +import { + CodeWhispererSupplementalContext, + CodeWhispererSupplementalContextItem, + SupplementalContextStrategy, +} from '../../models/model' +import { LspController } from '../../../amazonq/lsp/lspController' +import { waitUntil } from '../../../shared/utilities/timeoutUtils' type CrossFileSupportedLanguage = | 'java' @@ -48,36 +51,162 @@ interface Chunk { score?: number } +/** + * `none`: supplementalContext is not supported + * `opentabs`: opentabs_BM25 + * `codemap`: repomap + opentabs BM25 + * `bm25`: global_BM25 + * `default`: repomap + global_BM25 + */ +type SupplementalContextConfig = 'none' | 'opentabs' | 'codemap' | 'bm25' | 'default' + export async function fetchSupplementalContextForSrc( editor: vscode.TextEditor, cancellationToken: vscode.CancellationToken ): Promise | undefined> { - const shouldProceed = shouldFetchCrossFileContext( - editor.document.languageId, - CodeWhispererUserGroupSettings.instance.userGroup + const supplementalContextConfig = getSupplementalContextConfig(editor.document.languageId) + + // not supported case + if (supplementalContextConfig === 'none') { + return undefined + } + + // fallback to opentabs if projectContext timeout + const opentabsContextPromise = waitUntil( + async function () { + return await fetchOpentabsContext(editor, cancellationToken) + }, + { timeout: supplementalContextTimeoutInMs, interval: 5, truthy: false } + ) + + // opentabs context will use bm25 and users' open tabs to fetch supplemental context + if (supplementalContextConfig === 'opentabs') { + const supContext = (await opentabsContextPromise) ?? [] + return { + supplementalContextItems: supContext, + strategy: supContext.length === 0 ? 'Empty' : 'opentabs', + } + } + + // codemap will use opentabs context plus repomap if it's present + if (supplementalContextConfig === 'codemap') { + let strategy: SupplementalContextStrategy = 'Empty' + let hasCodemap: boolean = false + let hasOpentabs: boolean = false + const opentabsContextAndCodemap = await waitUntil( + async function () { + const result: CodeWhispererSupplementalContextItem[] = [] + const opentabsContext = await fetchOpentabsContext(editor, cancellationToken) + const codemap = await fetchProjectContext(editor, 'codemap') + + if (codemap && codemap.length > 0) { + result.push(...codemap) + hasCodemap = true + } + + if (opentabsContext && opentabsContext.length > 0) { + result.push(...opentabsContext) + hasOpentabs = true + } + + return result + }, + { timeout: supplementalContextTimeoutInMs, interval: 5, truthy: false } + ) + + if (hasCodemap) { + strategy = 'codemap' + } else if (hasOpentabs) { + strategy = 'opentabs' + } else { + strategy = 'Empty' + } + + return { + supplementalContextItems: opentabsContextAndCodemap ?? [], + strategy: strategy, + } + } + + // global bm25 without repomap + if (supplementalContextConfig === 'bm25') { + const projectBM25Promise = waitUntil( + async function () { + return await fetchProjectContext(editor, 'bm25') + }, + { timeout: supplementalContextTimeoutInMs, interval: 5, truthy: false } + ) + + const [projectContext, opentabsContext] = await Promise.all([projectBM25Promise, opentabsContextPromise]) + if (projectContext && projectContext.length > 0) { + return { + supplementalContextItems: projectContext, + strategy: 'bm25', + } + } + + const supContext = opentabsContext ?? [] + return { + supplementalContextItems: supContext, + strategy: supContext.length === 0 ? 'Empty' : 'opentabs', + } + } + + // global bm25 with repomap + const projectContextAndCodemapPromise = waitUntil( + async function () { + return await fetchProjectContext(editor, 'default') + }, + { timeout: supplementalContextTimeoutInMs, interval: 5, truthy: false } ) - if (!shouldProceed) { - return shouldProceed === undefined - ? undefined - : { - supplementalContextItems: [], - strategy: 'Empty', - } + const [projectContext, opentabsContext] = await Promise.all([ + projectContextAndCodemapPromise, + opentabsContextPromise, + ]) + if (projectContext && projectContext.length > 0) { + return { + supplementalContextItems: projectContext, + strategy: 'default', + } + } + + return { + supplementalContextItems: opentabsContext ?? [], + strategy: 'opentabs', } +} +export async function fetchProjectContext( + editor: vscode.TextEditor, + target: 'default' | 'codemap' | 'bm25' +): Promise { + const inputChunkContent = getInputChunk(editor) + + const inlineProjectContext: { content: string; score: number; filePath: string }[] = + await LspController.instance.queryInlineProjectContext( + inputChunkContent.content, + editor.document.uri.fsPath, + target + ) + + return inlineProjectContext +} + +export async function fetchOpentabsContext( + editor: vscode.TextEditor, + cancellationToken: vscode.CancellationToken +): Promise { const codeChunksCalculated = crossFileContextConfig.numberOfChunkToFetch // Step 1: Get relevant cross files to refer const relevantCrossFilePaths = await getCrossFileCandidates(editor) - throwIfCancelled(cancellationToken) // Step 2: Split files to chunks with upper bound on chunkCount // We restrict the total number of chunks to improve on latency. // Chunk linking is required as we want to pass the next chunk value for matched chunk. let chunkList: Chunk[] = [] for (const relevantFile of relevantCrossFilePaths) { - throwIfCancelled(cancellationToken) const chunks: Chunk[] = await splitFileToChunks(relevantFile, crossFileContextConfig.numberOfLinesEachChunk) const linkedChunks = linkChunks(chunks) chunkList.push(...linkedChunks) @@ -91,14 +220,18 @@ export async function fetchSupplementalContextForSrc( // Step 3: Generate Input chunk (10 lines left of cursor position) // and Find Best K chunks w.r.t input chunk using BM25 - const inputChunk: Chunk = getInputChunk(editor, crossFileContextConfig.numberOfLinesEachChunk) + const inputChunk: Chunk = getInputChunk(editor) const bestChunks: Chunk[] = findBestKChunkMatches(inputChunk, chunkList, crossFileContextConfig.topK) - throwIfCancelled(cancellationToken) // Step 4: Transform best chunks to supplemental contexts const supplementalContexts: CodeWhispererSupplementalContextItem[] = [] + let totalLength = 0 for (const chunk of bestChunks) { - throwIfCancelled(cancellationToken) + totalLength += chunk.nextContent.length + + if (totalLength > crossFileContextConfig.maximumTotalLength) { + break + } supplementalContexts.push({ filePath: chunk.fileName, @@ -109,16 +242,13 @@ export async function fetchSupplementalContextForSrc( // DO NOT send code chunk with empty content getLogger().debug(`CodeWhisperer finished fetching crossfile context out of ${relevantCrossFilePaths.length} files`) - return { - supplementalContextItems: supplementalContexts.filter((item) => item.content.trim().length !== 0), - strategy: 'OpenTabs_BM25', - } + return supplementalContexts } function findBestKChunkMatches(chunkInput: Chunk, chunkReferences: Chunk[], k: number): Chunk[] { const chunkContentList = chunkReferences.map((chunk) => chunk.content) - //performBM25Scoring returns the output in a sorted order (descending of scores) + // performBM25Scoring returns the output in a sorted order (descending of scores) const top3: BM25Document[] = new BM25Okapi(chunkContentList).topN(chunkInput.content, crossFileContextConfig.topK) return top3.map((doc) => { @@ -137,7 +267,8 @@ function findBestKChunkMatches(chunkInput: Chunk, chunkReferences: Chunk[], k: n /* This extract 10 lines to the left of the cursor from trigger file. * This will be the inputquery to bm25 matching against list of cross-file chunks */ -function getInputChunk(editor: vscode.TextEditor, chunkSize: number) { +function getInputChunk(editor: vscode.TextEditor) { + const chunkSize = crossFileContextConfig.numberOfLinesEachChunk const cursorPosition = editor.selection.active const startLine = Math.max(cursorPosition.line - chunkSize, 0) const endLine = Math.max(cursorPosition.line - 1, 0) @@ -151,19 +282,19 @@ function getInputChunk(editor: vscode.TextEditor, chunkSize: number) { /** * Util to decide if we need to fetch crossfile context since CodeWhisperer CrossFile Context feature is gated by userGroup and language level * @param languageId: VSCode language Identifier - * @param userGroup: CodeWhisperer user group settings, refer to userGroupUtil.ts * @returns specifically returning undefined if the langueage is not supported, * otherwise true/false depending on if the language is fully supported or not belonging to the user group */ -function shouldFetchCrossFileContext( - languageId: vscode.TextDocument['languageId'], - userGroup: UserGroup -): boolean | undefined { +function getSupplementalContextConfig(languageId: vscode.TextDocument['languageId']): SupplementalContextConfig { if (!isCrossFileSupported(languageId)) { - return undefined + return 'none' } - return true + const group = FeatureConfigProvider.instance.getProjectContextGroup() + switch (group) { + default: + return 'codemap' + } } /** @@ -171,7 +302,7 @@ function shouldFetchCrossFileContext( * when a given chunk context passes the match in BM25. * Special handling is needed for last(its next points to its own) and first chunk */ -function linkChunks(chunks: Chunk[]) { +export function linkChunks(chunks: Chunk[]) { const updatedChunks: Chunk[] = [] // This additional chunk is needed to create a next pointer to chunk 0. @@ -199,7 +330,7 @@ function linkChunks(chunks: Chunk[]) { export async function splitFileToChunks(filePath: string, chunkSize: number): Promise { const chunks: Chunk[] = [] - const fileContent = (await fs.readFileAsString(filePath)).trimEnd() + const fileContent = (await fs.readFileText(filePath)).trimEnd() const lines = fileContent.split('\n') for (let i = 0; i < lines.length; i += chunkSize) { @@ -248,9 +379,3 @@ export async function getCrossFileCandidates(editor: vscode.TextEditor): Promise return fileToDistance.file }) } - -function throwIfCancelled(token: vscode.CancellationToken): void | never { - if (token.isCancellationRequested) { - throw new ToolkitError(supplemetalContextFetchingTimeoutMsg, { cause: new CancellationError('timeout') }) - } -} diff --git a/packages/core/src/codewhisperer/util/supplementalContext/rankBm25.ts b/packages/core/src/codewhisperer/util/supplementalContext/rankBm25.ts index a6fd84aafd6..3e00013c9cf 100644 --- a/packages/core/src/codewhisperer/util/supplementalContext/rankBm25.ts +++ b/packages/core/src/codewhisperer/util/supplementalContext/rankBm25.ts @@ -7,7 +7,7 @@ export interface BM25Document { content: string - /** The score that the document recieves. */ + /** The score that the document receives. */ score: number index: number diff --git a/packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts b/packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts index 30ccbaf7f60..5cb7c2bfb83 100644 --- a/packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts +++ b/packages/core/src/codewhisperer/util/supplementalContext/supplementalContextUtil.ts @@ -39,7 +39,9 @@ export async function fetchSupplementalContext( return { isUtg: isUtg, isProcessTimeout: false, - supplementalContextItems: value.supplementalContextItems, + supplementalContextItems: value.supplementalContextItems.filter( + (item) => item.content.trim().length !== 0 + ), contentsLength: value.supplementalContextItems.reduce((acc, curr) => acc + curr.content.length, 0), latency: performance.now() - timesBeforeFetching, strategy: value.strategy, diff --git a/packages/core/src/codewhisperer/util/supplementalContext/utgUtils.ts b/packages/core/src/codewhisperer/util/supplementalContext/utgUtils.ts index 96968b2377b..63c29dc1c9a 100644 --- a/packages/core/src/codewhisperer/util/supplementalContext/utgUtils.ts +++ b/packages/core/src/codewhisperer/util/supplementalContext/utgUtils.ts @@ -18,8 +18,6 @@ import { ToolkitError } from '../../../shared/errors' import { supplemetalContextFetchingTimeoutMsg } from '../../models/constants' import { CancellationError } from '../../../shared/utilities/timeoutUtils' import { utgConfig } from '../../models/constants' -import { CodeWhispererUserGroupSettings } from '../userGroupUtil' -import { UserGroup } from '../../models/constants' import { getOpenFilesInWindow } from '../../../shared/utilities/editorUtilities' import { getLogger } from '../../../shared/logger/logger' import { CodeWhispererSupplementalContext, CodeWhispererSupplementalContextItem, UtgStrategy } from '../../models/model' @@ -32,19 +30,12 @@ function isUtgSupportedLanguage(languageId: vscode.TextDocument['languageId']): return utgSupportedLanguages.includes(languageId) } -export function shouldFetchUtgContext( - languageId: vscode.TextDocument['languageId'], - userGroup: UserGroup -): boolean | undefined { +export function shouldFetchUtgContext(languageId: vscode.TextDocument['languageId']): boolean | undefined { if (!isUtgSupportedLanguage(languageId)) { return undefined } - if (languageId === 'java') { - return true - } else { - return userGroup === UserGroup.CrossFile - } + return languageId === 'java' } /** @@ -60,10 +51,7 @@ export async function fetchSupplementalContextForTest( editor: vscode.TextEditor, cancellationToken: vscode.CancellationToken ): Promise | undefined> { - const shouldProceed = shouldFetchUtgContext( - editor.document.languageId, - CodeWhispererUserGroupSettings.instance.userGroup - ) + const shouldProceed = shouldFetchUtgContext(editor.document.languageId) if (!shouldProceed) { return shouldProceed === undefined ? undefined : { supplementalContextItems: [], strategy: 'Empty' } @@ -116,7 +104,7 @@ async function generateSupplementalContextFromFocalFile( strategy: UtgStrategy, cancellationToken: vscode.CancellationToken ): Promise { - const fileContent = await fs.readFileAsString(vscode.Uri.parse(filePath!).fsPath) + const fileContent = await fs.readFileText(vscode.Uri.parse(filePath!).fsPath) // DO NOT send code chunk with empty content if (fileContent.trim().length === 0) { @@ -136,12 +124,12 @@ async function findSourceFileByContent( languageConfig: utgLanguageConfig, cancellationToken: vscode.CancellationToken ): Promise { - const testFileContent = await fs.readFileAsString(editor.document.fileName) + const testFileContent = await fs.readFileText(editor.document.fileName) const testElementList = extractFunctions(testFileContent, languageConfig.functionExtractionPattern) throwIfCancelled(cancellationToken) - testElementList.push(...extractClasses(editor.document.fileName, languageConfig.classExtractionPattern)) + testElementList.push(...extractClasses(testFileContent, languageConfig.classExtractionPattern)) throwIfCancelled(cancellationToken) @@ -161,7 +149,7 @@ async function findSourceFileByContent( for (const filePath of relevantFilePaths) { throwIfCancelled(cancellationToken) - const fileContent = await fs.readFileAsString(filePath) + const fileContent = await fs.readFileText(filePath) const elementList = extractFunctions(fileContent, languageConfig.functionExtractionPattern) elementList.push(...extractClasses(fileContent, languageConfig.classExtractionPattern)) const matchCount = countSubstringMatches(elementList, testElementList) diff --git a/packages/core/src/codewhisperer/util/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts index 9c5fe6b1856..9518aa610fc 100644 --- a/packages/core/src/codewhisperer/util/telemetryHelper.ts +++ b/packages/core/src/codewhisperer/util/telemetryHelper.ts @@ -18,7 +18,6 @@ import { import { CodewhispererCompletionType, CodewhispererSuggestionState } from '../../shared/telemetry/telemetry' import { getImportCount } from './importAdderUtil' import { CodeWhispererSettings } from './codewhispererSettings' -import { CodeWhispererUserGroupSettings } from './userGroupUtil' import { getSelectedCustomization } from './customizationUtil' import { AuthUtil } from './authUtil' import { isAwsError } from '../../shared/errors' @@ -27,11 +26,12 @@ import { session } from './codeWhispererSession' import { CodeWhispererSupplementalContext } from '../models/model' import { FeatureConfigProvider } from '../../shared/featureConfig' import { CodeScanRemediationsEventType } from '../client/codewhispereruserclient' +import { CodeAnalysisScope as CodeAnalysisScopeClientSide } from '../models/constants' +import { Session } from '../../amazonqTest/chat/session/session' export class TelemetryHelper { // Some variables for client component latency private sdkApiCallEndTime = 0 - private firstSuggestionShowTime = 0 private allPaginationEndTime = 0 private firstResponseRequestId = '' // variables for user trigger decision @@ -42,8 +42,6 @@ export class TelemetryHelper { private typeAheadLength = 0 private timeSinceLastModification = 0 private lastTriggerDecisionTime = 0 - private invocationTime = 0 - private timeToFirstRecommendation = 0 private classifierResult?: number = undefined private classifierThreshold?: number = undefined // variables for tracking end to end sessions @@ -60,6 +58,50 @@ export class TelemetryHelper { return (this.#instance ??= new this()) } + public sendTestGenerationToolkitEvent( + session: Session, + isSupportedLanguage: boolean, + result: 'Succeeded' | 'Failed' | 'Cancelled', + requestId?: string, + perfClientLatency?: number, + reasonDesc?: string, + isCodeBlockSelected?: boolean, + artifactsUploadDuration?: number, + buildPayloadBytes?: number, + buildZipFileBytes?: number, + acceptedCharactersCount?: number, + acceptedCount?: number, + acceptedLinesCount?: number, + generatedCharactersCount?: number, + generatedCount?: number, + generatedLinesCount?: number, + reason?: string + ) { + telemetry.amazonq_utgGenerateTests.emit({ + cwsprChatProgrammingLanguage: session.fileLanguage ?? 'plaintext', + hasUserPromptSupplied: session.hasUserPromptSupplied, + isSupportedLanguage: isSupportedLanguage, + result: result, + artifactsUploadDuration: artifactsUploadDuration, + buildPayloadBytes: buildPayloadBytes, + buildZipFileBytes: buildZipFileBytes, + credentialStartUrl: AuthUtil.instance.startUrl, + acceptedCharactersCount: acceptedCharactersCount, + acceptedCount: acceptedCount, + acceptedLinesCount: acceptedLinesCount, + generatedCharactersCount: generatedCharactersCount, + generatedCount: generatedCount, + generatedLinesCount: generatedLinesCount, + isCodeBlockSelected: isCodeBlockSelected, + jobGroup: session.testGenerationJobGroupName, + jobId: session.listOfTestGenerationJobId[0], + perfClientLatency: perfClientLatency, + requestId: requestId, + reasonDesc: reasonDesc, + reason: reason, + }) + } + public recordServiceInvocationTelemetry( requestId: string, sessionId: string, @@ -72,26 +114,25 @@ export class TelemetryHelper { supplementalContextMetadata?: CodeWhispererSupplementalContext | undefined ) { const event = { - codewhispererRequestId: requestId ? requestId : undefined, - codewhispererSessionId: sessionId ? sessionId : undefined, - codewhispererLastSuggestionIndex: lastSuggestionIndex, - codewhispererTriggerType: session.triggerType, codewhispererAutomatedTriggerType: session.autoTriggerType, - result, - duration: duration || 0, - codewhispererLineNumber: session.startPos.line, codewhispererCursorOffset: session.startCursorOffset, - codewhispererLanguage: language, + codewhispererCustomizationArn: getSelectedCustomization().arn, CodewhispererGettingStartedTask: taskType, - reason: reason ? reason.substring(0, 200) : undefined, - credentialStartUrl: AuthUtil.instance.startUrl, codewhispererImportRecommendationEnabled: CodeWhispererSettings.instance.isImportRecommendationEnabled(), - codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererLastSuggestionIndex: lastSuggestionIndex, + codewhispererLanguage: language, + codewhispererLineNumber: session.startPos.line, + codewhispererRequestId: requestId ? requestId : undefined, + codewhispererSessionId: sessionId ? sessionId : undefined, codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLatency: supplementalContextMetadata?.latency, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, - codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(), - codewhispererCustomizationArn: getSelectedCustomization().arn, + codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererTriggerType: session.triggerType, + credentialStartUrl: AuthUtil.instance.startUrl, + duration: duration || 0, + reason: reason ? reason.substring(0, 200) : undefined, + result, traceId: this.traceId, } telemetry.codewhisperer_serviceInvocation.emit(event) @@ -104,25 +145,94 @@ export class TelemetryHelper { language: CodewhispererLanguage, supplementalContextMetadata?: CodeWhispererSupplementalContext | undefined ) { + const selectedCustomization = getSelectedCustomization() + telemetry.codewhisperer_userDecision.emit({ + codewhispererCompletionType: 'Line', + codewhispererGettingStartedTask: session.taskType, + codewhispererLanguage: language, + codewhispererPaginationProgress: paginationIndex, codewhispererRequestId: requestIdList[0], codewhispererSessionId: sessionId ? sessionId : undefined, - codewhispererPaginationProgress: paginationIndex, - codewhispererTriggerType: session.triggerType, codewhispererSuggestionIndex: -1, codewhispererSuggestionState: 'Empty', - codewhispererSuggestionReferences: undefined, codewhispererSuggestionReferenceCount: 0, + codewhispererSuggestionReferences: undefined, + codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, + codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, + codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererTriggerType: session.triggerType, + credentialStartUrl: AuthUtil.instance.startUrl, + traceId: this.traceId, + }) + + telemetry.codewhisperer_userTriggerDecision.emit({ + codewhispererAutomatedTriggerType: session.autoTriggerType, + codewhispererClassifierResult: this.classifierResult, + codewhispererClassifierThreshold: this.classifierThreshold, codewhispererCompletionType: 'Line', - codewhispererLanguage: language, + codewhispererCursorOffset: session.startCursorOffset, + codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, + codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), + codewhispererFirstRequestId: requestIdList[0], codewhispererGettingStartedTask: session.taskType, - credentialStartUrl: AuthUtil.instance.startUrl, - codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(), - codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererLanguage: language, + codewhispererLineNumber: session.startPos.line, + codewhispererPreviousSuggestionState: this.prevTriggerDecision, + codewhispererSessionId: sessionId, + codewhispererSuggestionCount: 0, + codewhispererSuggestionImportCount: 0, + codewhispererSuggestionState: 'Empty', codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, + // eslint-disable-next-line id-length + codewhispererSupplementalContextStrategyId: supplementalContextMetadata?.strategy, + codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererTimeSinceLastDocumentChange: this.timeSinceLastModification + ? this.timeSinceLastModification + : undefined, + codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime + ? performance.now() - this.lastTriggerDecisionTime + : undefined, + codewhispererTimeToFirstRecommendation: session.timeToFirstRecommendation, + codewhispererTriggerType: session.triggerType, + codewhispererTypeaheadLength: this.typeAheadLength, + credentialStartUrl: AuthUtil.instance.startUrl, traceId: this.traceId, }) + + client + .sendTelemetryEvent({ + telemetryEvent: { + userTriggerDecisionEvent: { + sessionId: sessionId, + requestId: requestIdList[0], + customizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, + programmingLanguage: { + languageName: runtimeLanguageContext.toRuntimeLanguage(language), + }, + completionType: 'LINE', + suggestionState: 'EMPTY', + recommendationLatencyMilliseconds: 0, + triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation, + perceivedLatencyMilliseconds: session.perceivedLatency, + timestamp: new Date(Date.now()), + suggestionReferenceCount: 0, + generatedLine: 0, + numberOfRecommendations: 0, + acceptedCharacterCount: 0, + }, + }, + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + + getLogger().error(`Failed to invoke sendTelemetryEvent, requestId: ${requestId ?? ''}`) + }) } /** @@ -158,30 +268,29 @@ export class TelemetryHelper { } const event: CodewhispererUserDecision = { // TODO: maintain a list of RecommendationContexts with both recommendation and requestId in it, instead of two separate list items. + codewhispererCompletionType: this.getCompletionType(i, completionTypes), + codewhispererGettingStartedTask: session.taskType, + codewhispererLanguage: session.language, + codewhispererPaginationProgress: paginationIndex, codewhispererRequestId: requestIdList[i], codewhispererSessionId: sessionId ? sessionId : undefined, - codewhispererPaginationProgress: paginationIndex, - codewhispererTriggerType: session.triggerType, + codewhispererSuggestionImportCount: getImportCount(_elem), codewhispererSuggestionIndex: i, codewhispererSuggestionState: this.getSuggestionState(i, acceptIndex, recommendationSuggestionState), - codewhispererSuggestionReferences: uniqueSuggestionReferences, codewhispererSuggestionReferenceCount: _elem.references ? _elem.references.length : 0, - codewhispererSuggestionImportCount: getImportCount(_elem), - codewhispererCompletionType: this.getCompletionType(i, completionTypes), - codewhispererLanguage: session.language, - codewhispererGettingStartedTask: session.taskType, - credentialStartUrl: AuthUtil.instance.startUrl, - codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(), - codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererSuggestionReferences: uniqueSuggestionReferences, codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, + codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererTriggerType: session.triggerType, + credentialStartUrl: AuthUtil.instance.startUrl, traceId: this.traceId, } telemetry.codewhisperer_userDecision.emit(event) events.push(event) }) - //aggregate suggestion references count + // aggregate suggestion references count const referenceCount = this.getAggregatedSuggestionReferenceCount(events) // aggregate user decision events at requestId level @@ -219,26 +328,25 @@ export class TelemetryHelper { return } const aggregated: CodewhispererUserTriggerDecision = { - codewhispererSessionId: sessionId, + codewhispererAutomatedTriggerType: session.autoTriggerType, + codewhispererCompletionType: events[0].codewhispererCompletionType, + codewhispererCursorOffset: session.startCursorOffset, codewhispererFirstRequestId: requestId, - credentialStartUrl: events[0].credentialStartUrl, - codewhispererLanguage: events[0].codewhispererLanguage, codewhispererGettingStartedTask: session.taskType, - codewhispererTriggerType: events[0].codewhispererTriggerType, - codewhispererCompletionType: events[0].codewhispererCompletionType, - codewhispererSuggestionCount: events.length, - codewhispererAutomatedTriggerType: session.autoTriggerType, + codewhispererLanguage: events[0].codewhispererLanguage, codewhispererLineNumber: session.startPos.line, - codewhispererCursorOffset: session.startCursorOffset, - codewhispererSuggestionState: this.getAggregatedSuggestionState(events), + codewhispererSessionId: sessionId, + codewhispererSuggestionCount: events.length, codewhispererSuggestionImportCount: events .map((e) => e.codewhispererSuggestionImportCount || 0) .reduce((a, b) => a + b, 0), - codewhispererTypeaheadLength: 0, - codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(), - codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererSuggestionState: this.getAggregatedSuggestionState(events), codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, + codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, + codewhispererTriggerType: events[0].codewhispererTriggerType, + codewhispererTypeaheadLength: 0, + credentialStartUrl: events[0].credentialStartUrl, traceId: this.traceId, } return aggregated @@ -269,53 +377,52 @@ export class TelemetryHelper { .reduce((a, b) => a + b, 0) const aggregated: CodewhispererUserTriggerDecision = { - codewhispererSessionId: this.sessionDecisions[0].codewhispererSessionId, - codewhispererFirstRequestId: this.sessionDecisions[0].codewhispererFirstRequestId, - credentialStartUrl: this.sessionDecisions[0].credentialStartUrl, + codewhispererAutomatedTriggerType: autoTriggerType, + codewhispererCharactersAccepted: acceptedRecommendationContent.length, + codewhispererClassifierResult: this.classifierResult, + codewhispererClassifierThreshold: this.classifierThreshold, codewhispererCompletionType: aggregatedCompletionType, - codewhispererLanguage: language, + codewhispererCursorOffset: this.sessionDecisions[0].codewhispererCursorOffset, + codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, + codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), + codewhispererFirstRequestId: this.sessionDecisions[0].codewhispererFirstRequestId, codewhispererGettingStartedTask: session.taskType, - codewhispererTriggerType: this.sessionDecisions[0].codewhispererTriggerType, - codewhispererSuggestionCount: suggestionCount, - codewhispererAutomatedTriggerType: autoTriggerType, + codewhispererLanguage: language, codewhispererLineNumber: this.sessionDecisions[0].codewhispererLineNumber, - codewhispererCursorOffset: this.sessionDecisions[0].codewhispererCursorOffset, + codewhispererPreviousSuggestionState: this.prevTriggerDecision, + codewhispererSessionId: this.sessionDecisions[0].codewhispererSessionId, + codewhispererSuggestionCount: suggestionCount, codewhispererSuggestionImportCount: this.sessionDecisions .map((e) => e.codewhispererSuggestionImportCount || 0) .reduce((a, b) => a + b, 0), - codewhispererTypeaheadLength: this.typeAheadLength, + codewhispererSuggestionState: aggregatedSuggestionState, + codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, + codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, + // eslint-disable-next-line id-length + codewhispererSupplementalContextStrategyId: supplementalContextMetadata?.strategy, + codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, codewhispererTimeSinceLastDocumentChange: this.timeSinceLastModification ? this.timeSinceLastModification : undefined, codewhispererTimeSinceLastUserDecision: this.lastTriggerDecisionTime ? performance.now() - this.lastTriggerDecisionTime : undefined, - codewhispererTimeToFirstRecommendation: this.timeToFirstRecommendation, + codewhispererTimeToFirstRecommendation: session.timeToFirstRecommendation, codewhispererTriggerCharacter: autoTriggerType === 'SpecialCharacters' ? this.triggerChar : undefined, - codewhispererSuggestionState: aggregatedSuggestionState, - codewhispererPreviousSuggestionState: this.prevTriggerDecision, - codewhispererClassifierResult: this.classifierResult, - codewhispererClassifierThreshold: this.classifierThreshold, - codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(), - codewhispererSupplementalContextTimeout: supplementalContextMetadata?.isProcessTimeout, - codewhispererSupplementalContextIsUtg: supplementalContextMetadata?.isUtg, - codewhispererSupplementalContextLength: supplementalContextMetadata?.contentsLength, - codewhispererCustomizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, - // eslint-disable-next-line id-length - codewhispererSupplementalContextStrategyId: supplementalContextMetadata?.strategy, - codewhispererCharactersAccepted: acceptedRecommendationContent.length, - codewhispererFeatureEvaluations: FeatureConfigProvider.instance.getFeatureConfigsTelemetry(), + codewhispererTriggerType: this.sessionDecisions[0].codewhispererTriggerType, + codewhispererTypeaheadLength: this.typeAheadLength, + credentialStartUrl: this.sessionDecisions[0].credentialStartUrl, traceId: this.traceId, } telemetry.codewhisperer_userTriggerDecision.emit(aggregated) this.prevTriggerDecision = this.getAggregatedSuggestionState(this.sessionDecisions) this.lastTriggerDecisionTime = performance.now() - // When we send a userTriggerDecision of Empty or Discard, we set the time users see the first - // suggestion to be now. - let e2eLatency = this.firstSuggestionShowTime - session.invokeSuggestionStartTime - if (e2eLatency < 0) { - e2eLatency = performance.now() - session.invokeSuggestionStartTime + // When we send a userTriggerDecision for neither Accept nor Reject, service side should not use this value + // and client side will set this value to 0.0. + let e2eLatency = session.firstSuggestionShowTime - session.invokeSuggestionStartTime + if (aggregatedSuggestionState !== 'Reject' && aggregatedSuggestionState !== 'Accept') { + e2eLatency = 0.0 } client @@ -333,11 +440,13 @@ export class TelemetryHelper { completionType: this.getSendTelemetryCompletionType(aggregatedCompletionType), suggestionState: this.getSendTelemetrySuggestionState(aggregatedSuggestionState), recommendationLatencyMilliseconds: e2eLatency, + triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation, + perceivedLatencyMilliseconds: session.perceivedLatency, timestamp: new Date(Date.now()), - triggerToResponseLatencyMilliseconds: this.timeToFirstRecommendation, suggestionReferenceCount: referenceCount, generatedLine: generatedLines, numberOfRecommendations: suggestionCount, + acceptedCharacterCount: acceptedRecommendationContent.length, }, }, }) @@ -383,16 +492,6 @@ export class TelemetryHelper { this.timeSinceLastModification = timeSinceLastModification } - public setInvocationStartTime(invocationTime: number) { - this.invocationTime = invocationTime - } - - public setTimeToFirstRecommendation(timeToFirstRecommendation: number) { - if (this.invocationTime) { - this.timeToFirstRecommendation = timeToFirstRecommendation - this.invocationTime - } - } - public setTraceId(traceId: string) { this.traceId = traceId } @@ -402,7 +501,8 @@ export class TelemetryHelper { this.triggerChar = '' this.typeAheadLength = 0 this.timeSinceLastModification = 0 - this.timeToFirstRecommendation = 0 + session.timeToFirstRecommendation = 0 + session.perceivedLatency = 0 this.classifierResult = undefined this.classifierThreshold = undefined } @@ -485,7 +585,7 @@ export class TelemetryHelper { session.sdkApiCallStartTime = 0 this.sdkApiCallEndTime = 0 session.fetchCredentialStartTime = 0 - this.firstSuggestionShowTime = 0 + session.firstSuggestionShowTime = 0 this.allPaginationEndTime = 0 this.firstResponseRequestId = '' } @@ -509,8 +609,8 @@ export class TelemetryHelper { } public setFirstSuggestionShowTime() { - if (this.firstSuggestionShowTime === 0 && this.sdkApiCallEndTime !== 0) { - this.firstSuggestionShowTime = performance.now() + if (session.firstSuggestionShowTime === 0 && this.sdkApiCallEndTime !== 0) { + session.firstSuggestionShowTime = performance.now() } } @@ -523,24 +623,23 @@ export class TelemetryHelper { // report client component latency after all pagination call finish // and at least one suggestion is shown to the user public tryRecordClientComponentLatency() { - if (this.firstSuggestionShowTime === 0 || this.allPaginationEndTime === 0) { + if (session.firstSuggestionShowTime === 0 || this.allPaginationEndTime === 0) { return } telemetry.codewhisperer_clientComponentLatency.emit({ - codewhispererRequestId: this.firstResponseRequestId, - codewhispererSessionId: session.sessionId, - codewhispererFirstCompletionLatency: this.sdkApiCallEndTime - session.sdkApiCallStartTime, - codewhispererEndToEndLatency: this.firstSuggestionShowTime - session.invokeSuggestionStartTime, codewhispererAllCompletionsLatency: this.allPaginationEndTime - session.sdkApiCallStartTime, - codewhispererPostprocessingLatency: this.firstSuggestionShowTime - this.sdkApiCallEndTime, + codewhispererCompletionType: 'Line', codewhispererCredentialFetchingLatency: session.sdkApiCallStartTime - session.fetchCredentialStartTime, + codewhispererCustomizationArn: getSelectedCustomization().arn, + codewhispererEndToEndLatency: session.firstSuggestionShowTime - session.invokeSuggestionStartTime, + codewhispererFirstCompletionLatency: this.sdkApiCallEndTime - session.sdkApiCallStartTime, + codewhispererLanguage: session.language, + codewhispererPostprocessingLatency: session.firstSuggestionShowTime - this.sdkApiCallEndTime, codewhispererPreprocessingLatency: session.fetchCredentialStartTime - session.invokeSuggestionStartTime, - codewhispererCompletionType: 'Line', + codewhispererRequestId: this.firstResponseRequestId, + codewhispererSessionId: session.sessionId, codewhispererTriggerType: session.triggerType, - codewhispererLanguage: session.language, credentialStartUrl: AuthUtil.instance.startUrl, - codewhispererUserGroup: CodeWhispererUserGroupSettings.getUserGroup().toString(), - codewhispererCustomizationArn: getSelectedCustomization().arn, }) } public sendCodeScanEvent(languageId: string, jobId: string) { @@ -573,6 +672,188 @@ export class TelemetryHelper { }) } + public sendCodeScanSucceededEvent( + language: string, + jobId: string, + numberOfFindings: number, + scope: CodeAnalysisScopeClientSide + ) { + client + .sendTelemetryEvent({ + telemetryEvent: { + codeScanSucceededEvent: { + programmingLanguage: { + languageName: runtimeLanguageContext.toRuntimeLanguage(language as CodewhispererLanguage), + }, + codeScanJobId: jobId, + numberOfFindings: numberOfFindings, + timestamp: new Date(Date.now()), + codeAnalysisScope: scope === CodeAnalysisScopeClientSide.FILE_AUTO ? 'FILE' : 'PROJECT', + }, + }, + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + + getLogger().debug( + `Failed to sendTelemetryEvent for code scan success, requestId: ${requestId ?? ''}, message: ${ + error.message + }` + ) + }) + } + + public sendCodeScanFailedEvent(language: string, jobId: string, scope: CodeAnalysisScopeClientSide) { + client + .sendTelemetryEvent({ + telemetryEvent: { + codeScanFailedEvent: { + programmingLanguage: { + languageName: runtimeLanguageContext.toRuntimeLanguage(language as CodewhispererLanguage), + }, + codeScanJobId: jobId, + codeAnalysisScope: scope === CodeAnalysisScopeClientSide.FILE_AUTO ? 'FILE' : 'PROJECT', + timestamp: new Date(Date.now()), + }, + }, + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + getLogger().debug( + `Failed to sendTelemetryEvent for code scan failure, requestId: ${requestId ?? ''}, message: ${ + error.message + }` + ) + }) + } + + public sendCodeFixGenerationEvent( + jobId: string, + language?: string, + ruleId?: string, + detectorId?: string, + linesOfCodeGenerated?: number, + charsOfCodeGenerated?: number + ) { + client + .sendTelemetryEvent({ + telemetryEvent: { + codeFixGenerationEvent: { + programmingLanguage: { + languageName: runtimeLanguageContext.toRuntimeLanguage(language as CodewhispererLanguage), + }, + jobId, + ruleId, + detectorId, + linesOfCodeGenerated, + charsOfCodeGenerated, + }, + }, + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + getLogger().debug( + `Failed to sendTelemetryEvent for code fix generation, requestId: ${requestId ?? ''}, message: ${ + error.message + }` + ) + }) + } + + public sendCodeFixAcceptanceEvent( + jobId: string, + language?: string, + ruleId?: string, + detectorId?: string, + linesOfCodeAccepted?: number, + charsOfCodeAccepted?: number + ) { + client + .sendTelemetryEvent({ + telemetryEvent: { + codeFixAcceptanceEvent: { + programmingLanguage: { + languageName: runtimeLanguageContext.toRuntimeLanguage(language as CodewhispererLanguage), + }, + jobId, + ruleId, + detectorId, + linesOfCodeAccepted, + charsOfCodeAccepted, + }, + }, + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + getLogger().debug( + `Failed to sendTelemetryEvent for code fix acceptance, requestId: ${requestId ?? ''}, message: ${ + error.message + }` + ) + }) + } + + public sendTestGenerationEvent( + groupName: string, + jobId: string, + language?: string, + numberOfUnitTestCasesGenerated?: number, + numberOfUnitTestCasesAccepted?: number, + linesOfCodeGenerated?: number, + linesOfCodeAccepted?: number, + charsOfCodeGenerated?: number, + charsOfCodeAccepted?: number + ) { + client + .sendTelemetryEvent({ + telemetryEvent: { + testGenerationEvent: { + programmingLanguage: { + languageName: runtimeLanguageContext.toRuntimeLanguage(language as CodewhispererLanguage), + }, + jobId, + groupName, + ideCategory: 'VSCODE', + numberOfUnitTestCasesGenerated, + numberOfUnitTestCasesAccepted, + linesOfCodeGenerated, + linesOfCodeAccepted, + charsOfCodeGenerated, + charsOfCodeAccepted, + timestamp: new Date(Date.now()), + }, + }, + }) + .then() + .catch((error) => { + let requestId: string | undefined + if (isAwsError(error)) { + requestId = error.requestId + } + getLogger().debug( + `Failed to sendTelemetryEvent for test generation, requestId: ${requestId ?? ''}, message: ${ + error.message + }` + ) + }) + } + public sendCodeScanRemediationsEvent( languageId?: string, codeScanRemediationEventType?: CodeScanRemediationsEventType, diff --git a/packages/core/src/codewhisperer/util/zipUtil.ts b/packages/core/src/codewhisperer/util/zipUtil.ts index f8656536ad8..b75a6798ab2 100644 --- a/packages/core/src/codewhisperer/util/zipUtil.ts +++ b/packages/core/src/codewhisperer/util/zipUtil.ts @@ -5,16 +5,24 @@ import admZip from 'adm-zip' import * as vscode from 'vscode' import path from 'path' -import { tempDirPath } from '../../shared/filesystemUtilities' +import { tempDirPath, testGenerationLogsDir } from '../../shared/filesystemUtilities' import { getLogger } from '../../shared/logger' import * as CodeWhispererConstants from '../models/constants' import { ToolkitError } from '../../shared/errors' -import { fs } from '../../shared' +import { fs } from '../../shared/fs/fs' import { getLoggerForScope } from '../service/securityScanHandler' import { runtimeLanguageContext } from './runtimeLanguageContext' import { CodewhispererLanguage } from '../../shared/telemetry/telemetry.gen' import { CurrentWsFolders, collectFiles } from '../../shared/utilities/workspaceUtils' -import { FileSizeExceededError, NoSourceFilesError, ProjectSizeExceededError } from '../models/errors' +import { + FileSizeExceededError, + NoActiveFileError, + NoSourceFilesError, + ProjectSizeExceededError, +} from '../models/errors' +import { ZipUseCase } from '../models/constants' +import { ChildProcess, ChildProcessOptions } from '../../shared/utilities/processUtils' +import { removeAnsi } from '../../shared' export interface ZipMetadata { rootDir: string @@ -31,6 +39,14 @@ export const ZipConstants = { newlineRegex: /\r?\n/, gitignoreFilename: '.gitignore', knownBinaryFileExts: ['.class'], + codeDiffFilePath: 'codeDiff/code.diff', +} + +interface GitDiffOptions { + projectPath: string + projectName: string + filePath?: string + scope?: CodeWhispererConstants.CodeAnalysisScope } export class ZipUtil { @@ -43,7 +59,7 @@ export class ZipUtil { protected _totalLines: number = 0 protected _fetchedDirs: Set = new Set() protected _language: CodewhispererLanguage | undefined - + protected _timestamp: string = Date.now().toString() constructor() {} getFileScanPayloadSizeLimitInBytes(): number { @@ -59,6 +75,12 @@ export class ZipUtil { return workspaceFolders?.map((folder) => folder.uri.fsPath) ?? [] } + public getProjectPath(filePath: string) { + const fileUri = vscode.Uri.file(filePath) + const workspaceFolder = vscode.workspace.getWorkspaceFolder(fileUri) + return workspaceFolder?.uri.fsPath + } + protected async getTextContent(uri: vscode.Uri) { const document = await vscode.workspace.openTextDocument(uri) const content = document.getText() @@ -66,7 +88,10 @@ export class ZipUtil { } public reachSizeLimit(size: number, scope: CodeWhispererConstants.CodeAnalysisScope): boolean { - if (scope === CodeWhispererConstants.CodeAnalysisScope.FILE) { + if ( + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO || + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND + ) { return size > this.getFileScanPayloadSizeLimitInBytes() } else { return size > this.getProjectScanPayloadSizeLimitInBytes() @@ -78,9 +103,9 @@ export class ZipUtil { return willReachLimit } - protected async zipFile(uri: vscode.Uri | undefined) { + protected async zipFile(uri: vscode.Uri | undefined, scope: CodeWhispererConstants.CodeAnalysisScope) { if (!uri) { - throw Error('Uri is undefined') + throw new NoActiveFileError() } const zip = new admZip() @@ -88,10 +113,17 @@ export class ZipUtil { const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri) if (workspaceFolder) { - const projectName = workspaceFolder.name + // Note: workspaceFolder.name is not the same as the file system folder name, + // use the fsPath value instead + const projectName = path.basename(workspaceFolder.uri.fsPath) const relativePath = vscode.workspace.asRelativePath(uri) const zipEntryPath = this.getZipEntryPath(projectName, relativePath) zip.addFile(zipEntryPath, Buffer.from(content, 'utf-8')) + + if (scope === CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND) { + const gitDiffContent = `+++ b/${path.normalize(zipEntryPath)}` // Sending file path in payload for LLM code review + zip.addFile(ZipConstants.codeDiffFilePath, Buffer.from(gitDiffContent, 'utf-8')) + } } else { zip.addFile(uri.fsPath, Buffer.from(content, 'utf-8')) } @@ -100,44 +132,278 @@ export class ZipUtil { this._totalSize += (await fs.stat(uri.fsPath)).size this._totalLines += content.split(ZipConstants.newlineRegex).length - if (this.reachSizeLimit(this._totalSize, CodeWhispererConstants.CodeAnalysisScope.FILE)) { + if (this.reachSizeLimit(this._totalSize, scope)) { throw new FileSizeExceededError() } - - const zipFilePath = this.getZipDirPath() + CodeWhispererConstants.codeScanZipExt + const zipFilePath = this.getZipDirPath(ZipUseCase.CODE_SCAN) + CodeWhispererConstants.codeScanZipExt zip.writeZip(zipFilePath) return zipFilePath } protected getZipEntryPath(projectName: string, relativePath: string) { - // Workspaces with multiple folders have the folder names as the root folder, - // but workspaces with only a single folder don't. So prepend the workspace folder name - // if it is not present. - return relativePath.split('/').shift() === projectName ? relativePath : path.join(projectName, relativePath) + return path.join(projectName, relativePath) } - protected async zipProject() { - const zip = new admZip() + /** + * Processes a directory and adds its contents to a zip archive while preserving the directory structure. + * + * @param zip - The AdmZip instance to add files and directories to + * @param metadataDir - The absolute path to the directory to process + * + * @remarks + * This function: + * - Creates empty directory entries in the zip for each directory + * - Recursively processes all subdirectories + * - Adds all files to the zip while maintaining relative paths + * - Handles errors for individual file operations without stopping the overall process + * + * The files in the zip will be stored under a root directory named after the input directory's basename. + * + * @throws May throw errors from filesystem operations or zip creation + * + * @example + * ```typescript + * const zip = new AdmZip(); + * await processMetadataDir(zip, '/path/to/directory'); + * ``` + */ + protected async processMetadataDir(zip: admZip, metadataDir: string) { + const metadataDirName = path.basename(metadataDir) + // Helper function to add empty directory to zip + const addEmptyDirectory = (dirPath: string) => { + const relativePath = path.relative(metadataDir, dirPath) + const pathWithMetadata = path.join(metadataDirName, relativePath, '/') + zip.addFile(pathWithMetadata, Buffer.from('')) + } - const projectPaths = this.getProjectPaths() + // Recursive function to process directories + const processDirectory = async (dirPath: string) => { + const entries = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath)) + addEmptyDirectory(dirPath) + + for (const [fileName, fileType] of entries) { + const filePath = path.join(dirPath, fileName) + + if (fileType === vscode.FileType.File) { + try { + const fileContent = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath)) + const buffer = Buffer.from(fileContent) + const relativePath = path.relative(metadataDir, filePath) + const pathWithMetadata = path.join(metadataDirName, relativePath) + zip.addFile(pathWithMetadata, buffer) + } catch (error) { + getLogger().error(`Failed to add file ${filePath} to zip: ${error}`) + } + } else if (fileType === vscode.FileType.Directory) { + // Recursively process subdirectory + await processDirectory(filePath) + } + } + } + await processDirectory(metadataDir) + } + + protected async zipProject(useCase: ZipUseCase, projectPath?: string, metadataDir?: string) { + const zip = new admZip() + let projectPaths = [] + if (useCase === ZipUseCase.TEST_GENERATION && projectPath) { + projectPaths.push(projectPath) + } else { + projectPaths = this.getProjectPaths() + } + if (useCase === ZipUseCase.CODE_SCAN) { + await this.processCombinedGitDiff(zip, projectPaths, '', CodeWhispererConstants.CodeAnalysisScope.PROJECT) + } const languageCount = new Map() - await this.processSourceFiles(zip, languageCount, projectPaths) - this.processOtherFiles(zip, languageCount) + await this.processSourceFiles(zip, languageCount, projectPaths, useCase) + if (metadataDir) { + await this.processMetadataDir(zip, metadataDir) + } + if (useCase !== ZipUseCase.TEST_GENERATION) { + this.processOtherFiles(zip, languageCount) + } if (languageCount.size === 0) { throw new NoSourceFilesError() } this._language = [...languageCount.entries()].reduce((a, b) => (b[1] > a[1] ? b : a))[0] - const zipFilePath = this.getZipDirPath() + CodeWhispererConstants.codeScanZipExt + const zipFilePath = this.getZipDirPath(useCase) + CodeWhispererConstants.codeScanZipExt zip.writeZip(zipFilePath) return zipFilePath } + protected async processCombinedGitDiff( + zip: admZip, + projectPaths: string[], + filePath?: string, + scope?: CodeWhispererConstants.CodeAnalysisScope + ) { + let gitDiffContent = '' + for (const projectPath of projectPaths) { + const projectName = path.basename(projectPath) + // Get diff content + gitDiffContent += await this.executeGitDiff({ + projectPath, + projectName, + filePath, + scope, + }) + } + if (gitDiffContent) { + zip.addFile(ZipConstants.codeDiffFilePath, Buffer.from(gitDiffContent, 'utf-8')) + } + } + + private async getGitUntrackedFiles(projectPath: string): Promise { + const checkNewFileArgs = ['ls-files', '--others', '--exclude-standard'] + const checkProcess = new ChildProcess('git', checkNewFileArgs) + + try { + let output = '' + await checkProcess.run({ + rejectOnError: true, + rejectOnErrorCode: true, + onStdout: (text) => { + output += text + }, + spawnOptions: { + cwd: projectPath, + }, + }) + return output + } catch (err) { + getLogger().warn(`Failed to check if file is new: ${err}`) + return undefined + } + } + + private async generateNewFileDiff(projectPath: string, projectName: string, relativePath: string): Promise { + let diffContent = '' + + const gitArgs = [ + 'diff', + '--no-index', + `--src-prefix=a/${projectName}/`, + `--dst-prefix=b/${projectName}/`, + '/dev/null', // Use /dev/null as the old file + relativePath, + ] + + const childProcess = new ChildProcess('git', gitArgs) + const runOptions: ChildProcessOptions = { + rejectOnError: false, + rejectOnErrorCode: false, + onStdout: (text) => { + diffContent += text + getLogger().verbose(removeAnsi(text)) + }, + onStderr: (text) => { + getLogger().error(removeAnsi(text)) + }, + spawnOptions: { + cwd: projectPath, + }, + } + + try { + await childProcess.run(runOptions) + return diffContent + } catch (err) { + getLogger().warn(`Failed to run diff command: ${err}`) + return '' + } + } + + private async generateHeadDiff(projectPath: string, projectName: string, relativePath?: string): Promise { + let diffContent = '' + + const gitArgs = [ + 'diff', + 'HEAD', + `--src-prefix=a/${projectName}/`, + `--dst-prefix=b/${projectName}/`, + ...(relativePath ? [relativePath] : []), + ] + + const childProcess = new ChildProcess('git', gitArgs) + + const runOptions: ChildProcessOptions = { + rejectOnError: true, + rejectOnErrorCode: true, + onStdout: (text) => { + diffContent += text + getLogger().verbose(removeAnsi(text)) + }, + onStderr: (text) => { + getLogger().error(removeAnsi(text)) + }, + spawnOptions: { + cwd: projectPath, + }, + } + + try { + await childProcess.run(runOptions) + return diffContent + } catch (err) { + getLogger().warn(`Failed to run command \`${childProcess.toString()}\`: ${err}`) + return '' + } + } + + private async executeGitDiff(options: GitDiffOptions): Promise { + const { projectPath, projectName, filePath, scope } = options + const isProjectScope = scope === CodeWhispererConstants.CodeAnalysisScope.PROJECT + + const untrackedFilesString = await this.getGitUntrackedFiles(projectPath) + const untrackedFilesArray = untrackedFilesString?.trim()?.split('\n')?.filter(Boolean) + + if (isProjectScope && untrackedFilesArray && !untrackedFilesArray.length) { + return await this.generateHeadDiff(projectPath, projectName) + } + + let diffContent = '' + + if (isProjectScope) { + diffContent = await this.generateHeadDiff(projectPath, projectName) + + if (untrackedFilesArray) { + const untrackedDiffs = await Promise.all( + untrackedFilesArray.map((file) => this.generateNewFileDiff(projectPath, projectName, file)) + ) + diffContent += untrackedDiffs.join('') + } + } else if (!isProjectScope && filePath) { + const relativeFilePath = path.relative(projectPath, filePath) + + const newFileDiff = await this.generateNewFileDiff(projectPath, projectName, relativeFilePath) + diffContent = this.rewriteDiff(newFileDiff) + } + return diffContent + } + + private rewriteDiff(inputStr: string): string { + const lines = inputStr.split('\n') + const rewrittenLines = lines.slice(0, 5).map((line) => { + line = line.replace(/\\\\/g, '/') + line = line.replace(/("a\/[^"]*)/g, (match, p1) => p1) + line = line.replace(/("b\/[^"]*)/g, (match, p1) => p1) + line = line.replace(/"/g, '') + + return line + }) + const outputLines = [...rewrittenLines, ...lines.slice(5)] + const outputStr = outputLines.join('\n') + + return outputStr + } + protected async processSourceFiles( zip: admZip, languageCount: Map, - projectPaths: string[] | undefined + projectPaths: string[] | undefined, + useCase: ZipUseCase ) { if (!projectPaths || projectPaths.length === 0) { return @@ -150,9 +416,13 @@ export class ZipUtil { this.getProjectScanPayloadSizeLimitInBytes() ) for (const file of sourceFiles) { - const zipEntryPath = this.getZipEntryPath(file.workspaceFolder.name, file.zipFilePath) + const projectName = path.basename(file.workspaceFolder.uri.fsPath) + const zipEntryPath = this.getZipEntryPath(projectName, file.relativeFilePath) if (ZipConstants.knownBinaryFileExts.includes(path.extname(file.fileUri.fsPath))) { + if (useCase === ZipUseCase.TEST_GENERATION) { + continue + } await this.processBinaryFile(zip, file.fileUri, zipEntryPath) } else { const isFileOpenAndDirty = this.isFileOpenAndDirty(file.fileUri) @@ -171,6 +441,27 @@ export class ZipUtil { ) } + protected async processTestCoverageFiles(targetPath: string) { + // TODO: will be removed post release + const coverageFilePatterns = ['**/coverage.xml', '**/coverage.json', '**/coverage.txt'] + let files: vscode.Uri[] = [] + + for (const pattern of coverageFilePatterns) { + files = await vscode.workspace.findFiles(pattern) + if (files.length > 0) { + break + } + } + + await Promise.all( + files.map(async (file) => { + const fileName = path.basename(file.path) + const targetFilePath = path.join(targetPath, fileName) + await fs.copy(file.path, targetFilePath) + }) + ) + } + protected processTextFile( zip: admZip, uri: vscode.Uri, @@ -221,12 +512,14 @@ export class ZipUtil { return vscode.workspace.textDocuments.some((document) => document.uri.fsPath === uri.fsPath && document.isDirty) } - protected getZipDirPath(): string { + public getZipDirPath(useCase: ZipUseCase): string { if (this._zipDir === '') { - this._zipDir = path.join( - this._tmpDir, - CodeWhispererConstants.codeScanTruncDirPrefix + '_' + Date.now().toString() - ) + const prefix = + useCase === ZipUseCase.TEST_GENERATION + ? CodeWhispererConstants.TestGenerationTruncDirPrefix + : CodeWhispererConstants.codeScanTruncDirPrefix + + this._zipDir = path.join(this._tmpDir, `${prefix}_${this._timestamp}`) } return this._zipDir } @@ -236,12 +529,15 @@ export class ZipUtil { scope: CodeWhispererConstants.CodeAnalysisScope ): Promise { try { - const zipDirPath = this.getZipDirPath() + const zipDirPath = this.getZipDirPath(ZipUseCase.CODE_SCAN) let zipFilePath: string - if (scope === CodeWhispererConstants.CodeAnalysisScope.FILE) { - zipFilePath = await this.zipFile(uri) + if ( + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_AUTO || + scope === CodeWhispererConstants.CodeAnalysisScope.FILE_ON_DEMAND + ) { + zipFilePath = await this.zipFile(uri, scope) } else if (scope === CodeWhispererConstants.CodeAnalysisScope.PROJECT) { - zipFilePath = await this.zipProject() + zipFilePath = await this.zipProject(ZipUseCase.CODE_SCAN) } else { throw new ToolkitError(`Unknown code analysis scope: ${scope}`) } @@ -259,12 +555,61 @@ export class ZipUtil { language: this._language, } } catch (error) { - getLogger().error('Zip error caused by:', error) + getLogger().error('Zip error caused by: %O', error) throw error } } - public async removeTmpFiles(zipMetadata: ZipMetadata, scope: CodeWhispererConstants.CodeAnalysisScope) { + public async generateZipTestGen(projectPath: string, initialExecution: boolean): Promise { + try { + // const repoMapFile = await LspClient.instance.getRepoMapJSON() + const zipDirPath = this.getZipDirPath(ZipUseCase.TEST_GENERATION) + + const metadataDir = path.join(zipDirPath, 'utgRequiredArtifactsDir') + + // Create directories + const dirs = { + metadata: metadataDir, + buildAndExecuteLogDir: path.join(metadataDir, 'buildAndExecuteLogDir'), + repoMapDir: path.join(metadataDir, 'repoMapData'), + testCoverageDir: path.join(metadataDir, 'testCoverageDir'), + } + await Promise.all(Object.values(dirs).map((dir) => fs.mkdir(dir))) + + // if (await fs.exists(repoMapFile)) { + // await fs.copy(repoMapFile, path.join(dirs.repoMapDir, 'repoMapData.json')) + // await fs.delete(repoMapFile) + // } + + if (!initialExecution) { + await this.processTestCoverageFiles(dirs.testCoverageDir) + + const sourcePath = path.join(testGenerationLogsDir, 'output.log') + const targetPath = path.join(dirs.buildAndExecuteLogDir, 'output.log') + if (await fs.exists(sourcePath)) { + await fs.copy(sourcePath, targetPath) + } + } + + const zipFilePath: string = await this.zipProject(ZipUseCase.TEST_GENERATION, projectPath, metadataDir) + const zipFileSize = (await fs.stat(zipFilePath)).size + return { + rootDir: zipDirPath, + zipFilePath: zipFilePath, + srcPayloadSizeInBytes: this._totalSize, + scannedFiles: new Set(this._pickedSourceFiles), + zipFileSizeInBytes: zipFileSize, + buildPayloadSizeInBytes: this._totalBuildSize, + lines: this._totalLines, + language: this._language, + } + } catch (error) { + getLogger().error('Zip error caused by: %s', error) + throw error + } + } + // TODO: Refactor this + public async removeTmpFiles(zipMetadata: ZipMetadata, scope?: CodeWhispererConstants.CodeAnalysisScope) { const logger = getLoggerForScope(scope) logger.verbose(`Cleaning up temporary files...`) await fs.delete(zipMetadata.zipFilePath, { force: true }) diff --git a/packages/core/src/codewhisperer/views/lineAnnotationController.ts b/packages/core/src/codewhisperer/views/lineAnnotationController.ts index a75ec39a7e9..12a52f9b4eb 100644 --- a/packages/core/src/codewhisperer/views/lineAnnotationController.ts +++ b/packages/core/src/codewhisperer/views/lineAnnotationController.ts @@ -38,6 +38,8 @@ function fromId(id: string | undefined): AnnotationState | undefined { return new TryMoreExState() case EndState.id: return new EndState() + case InlineChatState.id: + return new InlineChatState() default: return undefined } @@ -46,6 +48,7 @@ function fromId(id: string | undefined): AnnotationState | undefined { interface AnnotationState { id: string suppressWhileRunning: boolean + decorationRenderOptions?: vscode.ThemableDecorationAttachmentRenderOptions text: () => string updateState(changeSource: AnnotationChangeSource, force: boolean): AnnotationState | undefined @@ -199,6 +202,25 @@ export class EndState implements AnnotationState { } } +export class InlineChatState implements AnnotationState { + static id = 'amazonq_annotation_inline_chat' + id = InlineChatState.id + suppressWhileRunning = false + + text = () => { + if (os.platform() === 'darwin') { + return 'Amazon Q: Edit \u2318I' + } + return 'Amazon Q: Edit (Ctrl+I)' + } + updateState(_changeSource: AnnotationChangeSource, _force: boolean): AnnotationState { + return this + } + isNextState(_state: AnnotationState | undefined): boolean { + return false + } +} + /** * There are * - existing users @@ -308,12 +330,36 @@ export class LineAnnotationController implements vscode.Disposable { return this._currentState.id === new EndState().id } + isInlineChatHint(): boolean { + return this._currentState.id === new InlineChatState().id + } + async dismissTutorial() { this._currentState = new EndState() await setContext('aws.codewhisperer.tutorial.workInProgress', false) await globals.globalState.update(inlinehintKey, this._currentState.id) } + /** + * Trys to show the inline hint, if the tutorial is not finished it will not be shown + */ + async tryShowInlineHint(): Promise { + if (this.isTutorialDone()) { + this._isReady = true + this._currentState = new InlineChatState() + return true + } + return false + } + + async tryHideInlineHint(): Promise { + if (this._currentState instanceof InlineChatState) { + this._currentState = new EndState() + return true + } + return false + } + private async onActiveLinesChanged(e: LinesChangeEvent) { if (!this._isReady) { return @@ -384,7 +430,7 @@ export class LineAnnotationController implements vscode.Disposable { } // Disable Tips when language is not supported by Amazon Q. - if (!runtimeLanguageContext.isLanguageSupported(editor.document.languageId)) { + if (!runtimeLanguageContext.isLanguageSupported(editor.document)) { return } @@ -426,7 +472,9 @@ export class LineAnnotationController implements vscode.Disposable { decorationOptions.range = range await globals.globalState.update(inlinehintKey, this._currentState.id) - await setContext('aws.codewhisperer.tutorial.workInProgress', true) + if (!this.isInlineChatHint()) { + await setContext('aws.codewhisperer.tutorial.workInProgress', true) + } editor.setDecorations(this.cwLineHintDecoration, [decorationOptions]) } @@ -438,7 +486,7 @@ export class LineAnnotationController implements vscode.Disposable { ): Partial | undefined { const isCWRunning = RecommendationService.instance.isRunning - const textOptions = { + const textOptions: vscode.ThemableDecorationAttachmentRenderOptions = { contentText: '', fontWeight: 'normal', fontStyle: 'normal', diff --git a/packages/core/src/codewhisperer/views/securityIssue/securityIssueWebview.ts b/packages/core/src/codewhisperer/views/securityIssue/securityIssueWebview.ts index 2e49527eeab..7c1c655a937 100644 --- a/packages/core/src/codewhisperer/views/securityIssue/securityIssueWebview.ts +++ b/packages/core/src/codewhisperer/views/securityIssue/securityIssueWebview.ts @@ -6,14 +6,33 @@ import * as vscode from 'vscode' import { VueWebview } from '../../../webviews/main' import { CodeScanIssue } from '../../models/model' -import { Component } from '../../../shared/telemetry/telemetry' +import { + CodeFixAction, + CodewhispererCodeScanIssueApplyFix, + Component, + telemetry, +} from '../../../shared/telemetry/telemetry' +import { copyToClipboard } from '../../../shared/utilities/messages' +import { EditorContentController } from '../../../amazonq/commons/controllers/contentController' +import { SecurityIssueProvider } from '../../service/securityIssueProvider' +import { getPatchedCode, previewDiff } from '../../../shared/utilities/diffUtils' +import { amazonqCodeIssueDetailsTabTitle } from '../../models/constants' +import { AuthUtil } from '../../util/authUtil' +import { Mutable } from '../../../shared/utilities/tsUtils' +import { ExtContext } from '../../../shared/extensions' export class SecurityIssueWebview extends VueWebview { public static readonly sourcePath: string = 'src/codewhisperer/views/securityIssue/vue/index.js' public readonly id = 'aws.codeWhisperer.securityIssue' + public readonly onChangeIssue = new vscode.EventEmitter() + public readonly onChangeFilePath = new vscode.EventEmitter() + public readonly onChangeGenerateFixLoading = new vscode.EventEmitter() + public readonly onChangeGenerateFixError = new vscode.EventEmitter() private issue: CodeScanIssue | undefined private filePath: string | undefined + private isGenerateFixLoading: boolean = false + private isGenerateFixError: boolean = false public constructor() { super(SecurityIssueWebview.sourcePath) @@ -25,10 +44,12 @@ export class SecurityIssueWebview extends VueWebview { public setIssue(issue: CodeScanIssue) { this.issue = issue + this.onChangeIssue.fire(issue) } public setFilePath(filePath: string) { this.filePath = filePath + this.onChangeFilePath.fire(filePath) } public applyFix() { @@ -50,12 +71,12 @@ export class SecurityIssueWebview extends VueWebview { return '' } - public navigateToFile() { + public navigateToFile(showRange = true) { if (this.issue && this.filePath) { const range = new vscode.Range(this.issue.startLine, 0, this.issue.endLine, 0) return vscode.workspace.openTextDocument(this.filePath).then((doc) => { void vscode.window.showTextDocument(doc, { - selection: range, + selection: showRange ? range : undefined, viewColumn: vscode.ViewColumn.One, preview: true, }) @@ -68,6 +89,108 @@ export class SecurityIssueWebview extends VueWebview { this.dispose() } } + + public getIsGenerateFixLoading() { + return this.isGenerateFixLoading + } + + public setIsGenerateFixLoading(isGenerateFixLoading: boolean) { + this.isGenerateFixLoading = isGenerateFixLoading + this.onChangeGenerateFixLoading.fire(isGenerateFixLoading) + } + + public getIsGenerateFixError() { + return this.isGenerateFixError + } + + public setIsGenerateFixError(isGenerateFixError: boolean) { + this.isGenerateFixError = isGenerateFixError + this.onChangeGenerateFixError.fire(isGenerateFixError) + } + + public generateFix() { + void vscode.commands.executeCommand('aws.amazonq.security.generateFix', this.issue, this.filePath, 'webview') + } + + public regenerateFix() { + void vscode.commands.executeCommand('aws.amazonq.security.regenerateFix', this.issue, this.filePath, 'webview') + } + + public rejectFix() { + void vscode.commands.executeCommand('aws.amazonq.security.rejectFix', this.issue, this.filePath) + } + + public ignoreIssue() { + void vscode.commands.executeCommand('aws.amazonq.security.ignore', this.issue, this.filePath, 'webview') + } + + public ignoreAllIssues() { + void vscode.commands.executeCommand('aws.amazonq.security.ignoreAll', this.issue, 'webview') + } + + createApplyFixTelemetryEntry(fixAction: CodeFixAction): Mutable { + return { + detectorId: this.issue!.detectorId, + findingId: this.issue!.findingId, + ruleId: this.issue!.ruleId, + component: 'webview', + result: 'Succeeded', + credentialStartUrl: AuthUtil.instance.startUrl, + codeFixAction: fixAction, + } + } + + public async copyFixedCode() { + telemetry.ui_click.emit({ elementId: 'codeReviewGeneratedFix_copyCodeFix' }) + const fixedCode = await this.getFixedCode() + if (!fixedCode || fixedCode.length === 0) { + return + } + void copyToClipboard(fixedCode, 'suggested code fix') + const copyFixedCodeTelemetryEntry = this.createApplyFixTelemetryEntry('copyDiff') + telemetry.codewhisperer_codeScanIssueApplyFix.emit(copyFixedCodeTelemetryEntry) + } + + public async insertAtCursor() { + telemetry.ui_click.emit({ elementId: 'codeReviewGeneratedFix_insertCodeFixAtCursor' }) + const fixedCode = await this.getFixedCode() + if (!fixedCode || fixedCode.length === 0) { + return + } + const controller = new EditorContentController() + await this.navigateToFile(false) + controller.insertTextAtCursorPosition(fixedCode, () => {}) + const copyFixedCodeTelemetryEntry = this.createApplyFixTelemetryEntry('insertAtCursor') + telemetry.codewhisperer_codeScanIssueApplyFix.emit(copyFixedCodeTelemetryEntry) + } + + public async openDiff() { + telemetry.ui_click.emit({ elementId: 'codeReviewGeneratedFix_openCodeFixDiff' }) + const [suggestedFix] = this.issue?.suggestedFixes ?? [] + if (!this.filePath || !suggestedFix || !suggestedFix.code) { + return + } + await previewDiff(this.filePath, suggestedFix.code) + const copyFixedCodeTelemetryEntry = this.createApplyFixTelemetryEntry('openDiff') + telemetry.codewhisperer_codeScanIssueApplyFix.emit(copyFixedCodeTelemetryEntry) + } + + public async getLanguageId() { + if (!this.filePath) { + return + } + const document = await vscode.workspace.openTextDocument(this.filePath) + return document.languageId + } + + public async getFixedCode(snippetMode = true) { + const [suggestedFix] = this.issue?.suggestedFixes ?? [] + if (!this.filePath || !suggestedFix || !suggestedFix.code || !this.issue) { + return '' + } + const patchedCode = await getPatchedCode(this.filePath, suggestedFix.code, snippetMode) + return patchedCode + } } const Panel = VueWebview.compilePanel(SecurityIssueWebview) @@ -77,20 +200,85 @@ export async function showSecurityIssueWebview(ctx: vscode.ExtensionContext, iss activePanel ??= new Panel(ctx) activePanel.server.setIssue(issue) activePanel.server.setFilePath(filePath) + activePanel.server.setIsGenerateFixLoading(false) + activePanel.server.setIsGenerateFixError(false) const webviewPanel = await activePanel.show({ - title: 'Amazon Q Security Issue', + title: amazonqCodeIssueDetailsTabTitle, viewColumn: vscode.ViewColumn.Beside, cssFiles: ['securityIssue.css'], }) webviewPanel.iconPath = { - light: vscode.Uri.joinPath(ctx.extensionUri, 'resources/icons/vscode/light/shield.svg'), - dark: vscode.Uri.joinPath(ctx.extensionUri, 'resources/icons/vscode/dark/shield.svg'), + light: vscode.Uri.joinPath(ctx.extensionUri, 'resources/icons/aws/amazonq/q-squid-ink.svg'), + dark: vscode.Uri.joinPath(ctx.extensionUri, 'resources/icons/aws/amazonq/q-white.svg'), } webviewPanel.onDidDispose(() => (activePanel = undefined)) } +export function isSecurityIssueWebviewOpen() { + return activePanel !== undefined +} + export async function closeSecurityIssueWebview(findingId: string) { activePanel?.server.closeWebview(findingId) } + +export async function syncSecurityIssueWebview(context: ExtContext) { + const activeIssueId = activePanel?.server.getIssue()?.findingId + if (!activeIssueId) { + return + } + const updatedIssue = SecurityIssueProvider.instance.issues + .flatMap(({ issues }) => issues) + .find((issue) => issue.findingId === activeIssueId) + await updateSecurityIssueWebview({ + issue: updatedIssue, + context: context.extensionContext, + shouldRefreshView: false, + }) +} + +export async function getWebviewActiveIssueId() { + return activePanel?.server.getIssue()?.findingId +} + +type WebviewParams = { + issue?: CodeScanIssue + filePath?: string + isGenerateFixLoading?: boolean + isGenerateFixError?: boolean + shouldRefreshView: boolean + context: vscode.ExtensionContext +} +export async function updateSecurityIssueWebview({ + issue, + filePath, + isGenerateFixLoading, + isGenerateFixError, + shouldRefreshView, + context, +}: WebviewParams): Promise { + if (!activePanel) { + return + } + if (issue) { + activePanel.server.setIssue(issue) + } + if (filePath) { + activePanel.server.setFilePath(filePath) + } + if (isGenerateFixLoading !== undefined) { + activePanel.server.setIsGenerateFixLoading(isGenerateFixLoading) + } + if (isGenerateFixError !== undefined) { + activePanel.server.setIsGenerateFixError(isGenerateFixError) + } + if (shouldRefreshView && filePath && issue) { + await showSecurityIssueWebview(context, issue, filePath) + } +} + +export function getIsGenerateFixLoading() { + return activePanel?.server.getIsGenerateFixLoading() +} diff --git a/packages/core/src/codewhisperer/views/securityIssue/vue/root.vue b/packages/core/src/codewhisperer/views/securityIssue/vue/root.vue index 290e5bbfee7..a086aea3089 100644 --- a/packages/core/src/codewhisperer/views/securityIssue/vue/root.vue +++ b/packages/core/src/codewhisperer/views/securityIssue/vue/root.vue @@ -4,7 +4,6 @@

{{ title }}

-
@@ -15,7 +14,10 @@
- Common Weakness Enumeration (CWE) + Common Weakness
+ Enumeration (CWE)

@@ -86,6 +105,8 @@ import highSeverity from '../../../../../resources/images/severity-high.svg' import criticalSeverity from '../../../../../resources/images/severity-critical.svg' import markdownIt from 'markdown-it' import hljs from 'highlight.js' +import { parsePatch } from 'diff' +import { CodeScanIssue } from '../../../models/model' const client = WebviewClientFactory.create() const severityImages: Record = { @@ -97,10 +118,37 @@ const severityImages: Record = { } const md = markdownIt({ - highlight: function (str, lang) { - if (lang && hljs.getLanguage(lang)) { + highlight: function (str, lang, attrRaw): string { + const attrs = attrRaw.split(/\s+/g) + const showLineNumbers = attrs.includes('showLineNumbers') + const startFrom = parseInt(attrRaw.match(/startFrom=(\d+)/)?.[1] ?? '1') + const highlightStart = parseInt(attrRaw.match(/highlightStart=(\d+)/)?.[1] ?? '0') + const highlightEnd = parseInt(attrRaw.match(/highlightEnd=(\d+)/)?.[1] ?? '0') + if (lang) { try { - return hljs.highlight(str, { language: lang }).value + const highlighted = hljs.highlight(str, { + language: hljs.getLanguage(lang) ? lang : 'plaintext', + ignoreIllegals: true, + }).value + let result = highlighted + .trimEnd() + .split('\n') + .map((line) => { + if (line.startsWith('+')) { + return `${line}` + } else if (line.startsWith('-')) { + return `${line}` + } + return line + }) + .join('\n') + if (showLineNumbers) { + result = applyLineNumbers(result, startFrom - 1) + } + if (highlightStart && highlightEnd) { + result = applyHighlight(result, startFrom - 1, highlightStart, highlightEnd) + } + return result } catch (__) {} } @@ -108,6 +156,27 @@ const md = markdownIt({ }, }) +const applyLineNumbers = (code: string, lineNumberOffset = 0) => { + const lines = code.split('\n') + const rows = lines.map((line, idx) => { + const lineNumber = idx + 1 + lineNumberOffset + return `

${lineNumber}
${line}` + }) + return rows.join('\n') +} + +const applyHighlight = (code: string, lineNumberOffset = 0, highlightStart: number, highlightEnd: number) => { + const lines = code.split('\n') + const rows = lines.map((line, idx) => { + const lineNumber = idx + 1 + lineNumberOffset + if (lineNumber >= highlightStart && lineNumber < highlightEnd) { + return `
${line}
` + } + return line + }) + return rows.join('\n') +} + export default defineComponent({ data() { return { @@ -123,42 +192,116 @@ export default defineComponent({ isFixDescriptionAvailable: false, relatedVulnerabilities: [] as string[], startLine: 0, + endLine: 0, relativePath: '', + isGenerateFixLoading: false, + isGenerateFixError: false, + languageId: 'plaintext', + fixedCode: '', + referenceText: '', + referenceSpan: [0, 0], } }, created() { this.getData() + this.setupEventListeners() }, - updated() { + beforeMount() { this.getData() }, methods: { async getData() { const issue = await client.getIssue() - const relativePath = await client.getRelativePath() if (issue) { - const [suggestedFix] = issue.suggestedFixes - - this.title = issue.title - this.detectorId = issue.detectorId - this.detectorName = issue.detectorName - this.detectorUrl = issue.recommendation.url - this.relatedVulnerabilities = issue.relatedVulnerabilities - this.severity = issue.severity - this.recommendationText = issue.recommendation.text - this.startLine = issue.startLine - this.relativePath = relativePath - this.isFixAvailable = false - if (suggestedFix) { - this.isFixAvailable = true - this.suggestedFix = suggestedFix.code - if ( - suggestedFix.description.trim() !== '' && - suggestedFix.description.trim() !== 'Suggested remediation:' - ) { - this.isFixDescriptionAvailable = true - } - this.suggestedFixDescription = suggestedFix.description + this.updateFromIssue(issue) + } + const relativePath = await client.getRelativePath() + this.updateRelativePath(relativePath) + const isGenerateFixLoading = await client.getIsGenerateFixLoading() + const isGenerateFixError = await client.getIsGenerateFixError() + this.updateGenerateFixState(isGenerateFixLoading, isGenerateFixError) + const languageId = await client.getLanguageId() + if (languageId) { + this.updateLanguageId(languageId) + } + const fixedCode = await client.getFixedCode() + this.updateFixedCode(fixedCode) + }, + setupEventListeners() { + client.onChangeIssue(async (issue) => { + if (issue) { + this.updateFromIssue(issue) + } + const fixedCode = await client.getFixedCode() + this.updateFixedCode(fixedCode) + this.scrollTo('codeFixActions') + }) + client.onChangeFilePath(async (filePath) => { + const relativePath = await client.getRelativePath() + this.updateRelativePath(relativePath) + + const languageId = await client.getLanguageId() + if (languageId) { + this.updateLanguageId(languageId) + } + }) + client.onChangeGenerateFixLoading((isGenerateFixLoading) => { + this.isGenerateFixLoading = isGenerateFixLoading + this.scrollTo('codeFixSection') + }) + client.onChangeGenerateFixError((isGenerateFixError) => { + this.isGenerateFixError = isGenerateFixError + }) + }, + updateRelativePath(relativePath: string) { + this.relativePath = relativePath + }, + updateGenerateFixState(isGenerateFixLoading: boolean, isGenerateFixError: boolean) { + this.isGenerateFixLoading = isGenerateFixLoading + this.isGenerateFixError = isGenerateFixError + }, + updateLanguageId(languageId: string) { + this.languageId = languageId + }, + updateFixedCode(fixedCode: string) { + this.fixedCode = fixedCode.replaceAll('\n\\ No newline at end of file', '') + }, + updateFromIssue(issue: CodeScanIssue) { + const [suggestedFix] = issue.suggestedFixes + + this.title = issue.title + this.detectorId = issue.detectorId + this.detectorName = issue.detectorName + this.detectorUrl = issue.recommendation.url + this.relatedVulnerabilities = issue.relatedVulnerabilities + this.severity = issue.severity + this.recommendationText = issue.recommendation.text + this.startLine = issue.startLine + this.endLine = issue.endLine + this.isFixAvailable = false + this.isFixDescriptionAvailable = false + if (suggestedFix) { + this.isFixAvailable = !!suggestedFix.code && suggestedFix.code?.trim() !== '' + this.suggestedFix = suggestedFix.code ?? '' + if ( + suggestedFix.description?.trim() !== '' && + suggestedFix.description?.trim() !== 'Suggested remediation:' + ) { + this.isFixDescriptionAvailable = true + } + this.suggestedFixDescription = md.render(suggestedFix.description) + + const [reference] = suggestedFix.references ?? [] + if ( + reference && + reference.recommendationContentSpan?.start && + reference.recommendationContentSpan.end + ) { + this.referenceText = `Reference code under ${reference.licenseName} license from repository ${reference.repository}` + this.referenceSpan = [ + reference.recommendationContentSpan.start, + reference.recommendationContentSpan.end, + ] } } }, @@ -175,6 +318,60 @@ export default defineComponent({ navigateToFile() { client.navigateToFile() }, + generateFix() { + client.generateFix() + }, + regenerateFix() { + client.regenerateFix() + }, + rejectFix() { + client.rejectFix() + }, + ignoreIssue() { + client.ignoreIssue() + }, + ignoreAllIssues() { + client.ignoreAllIssues() + }, + copyFixedCode() { + client.copyFixedCode() + }, + insertAtCursor() { + client.insertAtCursor() + }, + openDiff() { + client.openDiff() + }, + computeSuggestedFixHtml() { + if (!this.isFixAvailable) { + return + } + const [parsedDiff] = parsePatch(this.suggestedFix) + const { oldStart } = parsedDiff.hunks[0] + const [referenceStart, referenceEnd] = this.referenceSpan + const htmlString = md.render(` +\`\`\`${this.languageId} showLineNumbers startFrom=${oldStart} ${ + referenceStart && referenceEnd + ? `highlightStart=${referenceStart + 1} highlightEnd=${referenceEnd + 1}` + : '' + } +${this.fixedCode} +\`\`\` + `) + const parser = new DOMParser() + const doc = parser.parseFromString(htmlString, 'text/html') + const referenceTracker = doc.querySelector('.reference-tracker') + if (referenceTracker) { + const tooltip = doc.createElement('div') + tooltip.classList.add('tooltip') + tooltip.innerHTML = this.referenceText + referenceTracker.appendChild(tooltip) + } + return doc.body.innerHTML + }, + scrollTo(refName: string) { + this.$nextTick(() => this.$refs?.[refName]?.scrollIntoView({ behavior: 'smooth' })) + }, }, computed: { severityImage() { @@ -184,11 +381,7 @@ export default defineComponent({ return md.render(this.recommendationText) }, suggestedFixHtml() { - return md.render(` -\`\`\`diff -${this.suggestedFix.replaceAll('\n\\ No newline at end of file', '')} -\`\`\` - `) + return this.computeSuggestedFixHtml() }, }, }) diff --git a/packages/core/src/codewhisperer/views/securityPanelViewProvider.ts b/packages/core/src/codewhisperer/views/securityPanelViewProvider.ts index e7fc67a6a67..d02eea155eb 100644 --- a/packages/core/src/codewhisperer/views/securityPanelViewProvider.ts +++ b/packages/core/src/codewhisperer/views/securityPanelViewProvider.ts @@ -214,7 +214,7 @@ export class SecurityPanelViewProvider implements vscode.WebviewViewProvider { private getHtmlContent(): string { if (this.persistLog.length === 0) { - return 'No security issues have been detected in the workspace.' + return 'No code issues have been detected in the workspace.' } return this.persistLog.join('') + this.dynamicLog.join('') } diff --git a/packages/core/src/codewhisperer/vue/backend.ts b/packages/core/src/codewhisperer/vue/backend.ts index e2561cad87c..e4baecadc18 100644 --- a/packages/core/src/codewhisperer/vue/backend.ts +++ b/packages/core/src/codewhisperer/vue/backend.ts @@ -29,7 +29,7 @@ export class CodeWhispererWebview extends VueWebview { private isFileSaved: boolean = false private getLocalFilePath(fileName: string): string { - //This will store the files in the global storage path of VSCode + // This will store the files in the global storage path of VSCode return path.join(globals.context.globalStorageUri.fsPath, fileName) } @@ -81,22 +81,22 @@ export class CodeWhispererWebview extends VueWebview { } } - //This function returns the OS type of the machine used in Shortcuts and Generate Suggestion Sections + // This function returns the OS type of the machine used in Shortcuts and Generate Suggestion Sections public getOSType(): OSType { return os.platform() === 'darwin' ? 'Mac' : 'RestOfOS' } - //This function opens the Keyboard shortcuts in VSCode + // This function opens the Keyboard shortcuts in VSCode async openShortCuts(): Promise { await vscode.commands.executeCommand('workbench.action.openGlobalKeybindings', 'codewhisperer') } - //This function opens the Feedback CodeWhisperer page in the webview + // This function opens the Feedback CodeWhisperer page in the webview async openFeedBack(): Promise { return submitFeedback(placeholder, 'Amazon Q') } - //------Telemetry------ + // ------Telemetry------ /** This represents the cause for the webview to open, whether a certain button was clicked or it opened automatically */ #codeWhispererSource?: CodeWhispererSource @@ -113,7 +113,7 @@ export class CodeWhispererWebview extends VueWebview { passive: true, }) } - //Telemetry for CodeWhisperer Try Example with two params Language and Task Type + // Telemetry for CodeWhisperer Try Example with two params Language and Task Type emitTryExampleClick(languageSelected: CodewhispererLanguage, taskType: CodewhispererGettingStartedTask) { telemetry.codewhisperer_onboardingClick.emit({ codewhispererLanguage: languageSelected, @@ -121,7 +121,7 @@ export class CodeWhispererWebview extends VueWebview { }) } } -//List of all events that are emitted from the webview of CodeWhisperer +// List of all events that are emitted from the webview of CodeWhisperer export type CodeWhispererUiClick = | 'codewhisperer_Resources_Documentation' | 'codewhisperer_Resources_Feedback' @@ -160,8 +160,8 @@ export async function showCodeWhispererWebview( }), ] const prompts = AmazonQPromptSettings.instance - //To check the condition If the user has already seen the welcome message - if (await prompts.isPromptEnabled('codeWhispererNewWelcomeMessage')) { + // To check the condition If the user has already seen the welcome message + if (prompts.isPromptEnabled('codeWhispererNewWelcomeMessage')) { telemetry.ui_click.emit({ elementId: 'codewhisperer_Learn_PageOpen', passive: true }) } else { telemetry.ui_click.emit({ elementId: 'codewhisperer_Learn_PageOpen', passive: false }) diff --git a/packages/core/src/codewhispererChat/app.ts b/packages/core/src/codewhispererChat/app.ts index f65352ecc10..6781cde30e5 100644 --- a/packages/core/src/codewhispererChat/app.ts +++ b/packages/core/src/codewhispererChat/app.ts @@ -10,6 +10,7 @@ import { AmazonQAppInitContext } from '../amazonq/apps/initContext' import { MessageListener } from '../amazonq/messages/messageListener' import { MessagePublisher } from '../amazonq/messages/messagePublisher' import { + ViewDiff, ChatItemFeedbackMessage, ChatItemVotedMessage, CopyCodeToClipboard, @@ -24,6 +25,7 @@ import { TabCreatedMessage, TriggerTabIDReceived, UIFocusMessage, + AcceptDiff, } from './controllers/chat/model' import { EditorContextCommand, registerCommands } from './commands/registerCommands' @@ -34,6 +36,8 @@ export function init(appContext: AmazonQAppInitContext) { processTabClosedMessage: new EventEmitter(), processTabChangedMessage: new EventEmitter(), processInsertCodeAtCursorPosition: new EventEmitter(), + processAcceptDiff: new EventEmitter(), + processViewDiff: new EventEmitter(), processCopyCodeToClipboard: new EventEmitter(), processContextMenuCommand: new EventEmitter(), processTriggerTabIDReceived: new EventEmitter(), @@ -62,6 +66,8 @@ export function init(appContext: AmazonQAppInitContext) { processInsertCodeAtCursorPosition: new MessageListener( cwChatControllerEventEmitters.processInsertCodeAtCursorPosition ), + processAcceptDiff: new MessageListener(cwChatControllerEventEmitters.processAcceptDiff), + processViewDiff: new MessageListener(cwChatControllerEventEmitters.processViewDiff), processCopyCodeToClipboard: new MessageListener( cwChatControllerEventEmitters.processCopyCodeToClipboard ), @@ -108,6 +114,8 @@ export function init(appContext: AmazonQAppInitContext) { processInsertCodeAtCursorPosition: new MessagePublisher( cwChatControllerEventEmitters.processInsertCodeAtCursorPosition ), + processAcceptDiff: new MessagePublisher(cwChatControllerEventEmitters.processAcceptDiff), + processViewDiff: new MessagePublisher(cwChatControllerEventEmitters.processViewDiff), processCopyCodeToClipboard: new MessagePublisher( cwChatControllerEventEmitters.processCopyCodeToClipboard ), diff --git a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts index 00326ddd955..fc164ebb95c 100644 --- a/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts +++ b/packages/core/src/codewhispererChat/clients/chat/v0/chat.ts @@ -3,10 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { SendMessageCommandOutput, SendMessageRequest } from '@amzn/amazon-q-developer-streaming-client' import { GenerateAssistantResponseCommandOutput, GenerateAssistantResponseRequest } from '@amzn/codewhisperer-streaming' import * as vscode from 'vscode' import { ToolkitError } from '../../../../shared/errors' import { createCodeWhispererChatStreamingClient } from '../../../../shared/clients/codewhispererChatClient' +import { createQDeveloperStreamingClient } from '../../../../shared/clients/qDeveloperChatClient' export class ChatSession { private sessionId?: string @@ -28,8 +30,28 @@ export class ChatSession { public setSessionID(id?: string) { this.sessionId = id } + async chatIam(chatRequest: SendMessageRequest): Promise { + const client = await createQDeveloperStreamingClient() - async chat(chatRequest: GenerateAssistantResponseRequest): Promise { + const response = await client.sendMessage(chatRequest) + if (!response.sendMessageResponse) { + throw new ToolkitError( + `Empty chat response. Session id: ${this.sessionId} Request ID: ${response.$metadata.requestId}` + ) + } + + const responseStream = response.sendMessageResponse + for await (const event of responseStream) { + if ('messageMetadataEvent' in event) { + this.sessionId = event.messageMetadataEvent?.conversationId + break + } + } + + return response + } + + async chatSso(chatRequest: GenerateAssistantResponseRequest): Promise { const client = await createCodeWhispererChatStreamingClient() if (this.sessionId !== undefined && chatRequest.conversationState !== undefined) { diff --git a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts index f853fb4611d..9ff56523379 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts @@ -4,14 +4,14 @@ */ import { + ConversationState, CursorState, DocumentSymbol, - GenerateAssistantResponseRequest, RelevantTextDocument, SymbolType, TextDocument, } from '@amzn/codewhisperer-streaming' -import { TriggerPayload } from '../model' +import { ChatTriggerType, TriggerPayload } from '../model' import { undefinedIfEmpty } from '../../../../shared' const fqnNameSizeDownLimit = 1 @@ -37,7 +37,7 @@ export const supportedLanguagesList = [ const filePathSizeLimit = 4_000 const customerMessageSizeLimit = 4_000 -export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): GenerateAssistantResponseRequest { +export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { conversationState: ConversationState } { let document: TextDocument | undefined = undefined let cursorState: CursorState | undefined = undefined @@ -98,6 +98,7 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): Gen const useRelevantDocuments = triggerPayload.useRelevantDocuments // service will throw validation exception if string is empty const customizationArn: string | undefined = undefinedIfEmpty(triggerPayload.customization.arn) + const chatTriggerType = triggerPayload.trigger === ChatTriggerType.InlineChatMessage ? 'INLINE_CHAT' : 'MANUAL' return { conversationState: { @@ -117,7 +118,7 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): Gen userIntent: triggerPayload.userIntent, }, }, - chatTriggerType: 'MANUAL', + chatTriggerType, customizationArn: customizationArn, }, } diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts index 88096621f56..6a1d388c05d 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts @@ -5,7 +5,7 @@ import { Event as VSCodeEvent, Uri } from 'vscode' import { EditorContextExtractor } from '../../editor/context/extractor' import { ChatSessionStorage } from '../../storages/chatSession' -import { Messenger, StaticTextResponseType } from './messenger/messenger' +import { Messenger, MessengerResponseType, StaticTextResponseType } from './messenger/messenger' import { PromptMessage, ChatTriggerType, @@ -24,6 +24,8 @@ import { ResponseBodyLinkClickMessage, ChatPromptCommandType, FooterInfoLinkClick, + ViewDiff, + AcceptDiff, } from './model' import { AppToWebViewMessageDispatcher } from '../../view/connector/connector' import { MessagePublisher } from '../../../amazonq/messages/messagePublisher' @@ -32,10 +34,8 @@ import { EditorContentController } from '../../../amazonq/commons/controllers/co import { EditorContextCommand } from '../../commands/registerCommands' import { PromptsGenerator } from './prompts/promptsGenerator' import { TriggerEventsStorage } from '../../storages/triggerEvents' -import { - CodeWhispererStreamingServiceException, - GenerateAssistantResponseCommandOutput, -} from '@amzn/codewhisperer-streaming' +import { SendMessageRequest } from '@amzn/amazon-q-developer-streaming-client' +import { CodeWhispererStreamingServiceException } from '@amzn/codewhisperer-streaming' import { UserIntentRecognizer } from './userIntent/userIntentRecognizer' import { CWCTelemetryHelper, recordTelemetryChatRunCommand } from './telemetryHelper' import { CodeWhispererTracker } from '../../../codewhisperer/tracker/codewhispererTracker' @@ -47,11 +47,13 @@ import { randomUUID } from '../../../shared/crypto' import { LspController } from '../../../amazonq/lsp/lspController' import { CodeWhispererSettings } from '../../../codewhisperer/util/codewhispererSettings' import { getSelectedCustomization } from '../../../codewhisperer/util/customizationUtil' -import { FeatureConfigProvider } from '../../../shared/featureConfig' import { getHttpStatusCode, AwsClientResponseError } from '../../../shared/errors' import { uiEventRecorder } from '../../../amazonq/util/eventRecorder' -import { globals } from '../../../shared' +import { globals, waitUntil } from '../../../shared' import { telemetry } from '../../../shared/telemetry' +import { isSsoConnection } from '../../../auth/connection' +import { inspect } from '../../../shared/utilities/collectionUtils' +import { DefaultAmazonQAppInitContext } from '../../../amazonq/apps/initContext' export interface ChatControllerMessagePublishers { readonly processPromptChatMessage: MessagePublisher @@ -59,6 +61,8 @@ export interface ChatControllerMessagePublishers { readonly processTabClosedMessage: MessagePublisher readonly processTabChangedMessage: MessagePublisher readonly processInsertCodeAtCursorPosition: MessagePublisher + readonly processAcceptDiff: MessagePublisher + readonly processViewDiff: MessagePublisher readonly processCopyCodeToClipboard: MessagePublisher readonly processContextMenuCommand: MessagePublisher readonly processTriggerTabIDReceived: MessagePublisher @@ -77,6 +81,8 @@ export interface ChatControllerMessageListeners { readonly processTabClosedMessage: MessageListener readonly processTabChangedMessage: MessageListener readonly processInsertCodeAtCursorPosition: MessageListener + readonly processAcceptDiff: MessageListener + readonly processViewDiff: MessageListener readonly processCopyCodeToClipboard: MessageListener readonly processContextMenuCommand: MessageListener readonly processTriggerTabIDReceived: MessageListener @@ -106,7 +112,7 @@ export class ChatController { ) { this.sessionStorage = new ChatSessionStorage() this.triggerEventsStorage = new TriggerEventsStorage() - this.telemetryHelper = new CWCTelemetryHelper(this.sessionStorage, this.triggerEventsStorage) + this.telemetryHelper = CWCTelemetryHelper.init(this.sessionStorage, this.triggerEventsStorage) this.messenger = new Messenger( new AppToWebViewMessageDispatcher(appsToWebViewMessagePublisher), this.telemetryHelper @@ -125,8 +131,9 @@ export class ChatController { }) this.chatControllerMessageListeners.processPromptChatMessage.onMessage((data) => { - if (data.traceId) { - uiEventRecorder.set(data.traceId, { + const uiEvents = uiEventRecorder.get(data.tabID) + if (uiEvents) { + uiEventRecorder.set(data.tabID, { events: { featureReceivedMessage: globals.clock.Date.now(), }, @@ -139,7 +146,7 @@ export class ChatController { **/ return telemetry.withTraceId(() => { return this.processPromptChatMessage(data) - }, data.traceId ?? randomUUID()) + }, uiEvents?.traceId ?? randomUUID()) }) this.chatControllerMessageListeners.processTabCreatedMessage.onMessage((data) => { @@ -158,6 +165,14 @@ export class ChatController { return this.processInsertCodeAtCursorPosition(data) }) + this.chatControllerMessageListeners.processAcceptDiff.onMessage((data) => { + return this.processAcceptDiff(data) + }) + + this.chatControllerMessageListeners.processViewDiff.onMessage((data) => { + return this.processViewDiff(data) + }) + this.chatControllerMessageListeners.processCopyCodeToClipboard.onMessage((data) => { return this.processCopyCodeToClipboard(data) }) @@ -277,6 +292,30 @@ export class ChatController { this.telemetryHelper.recordInteractWithMessage(message) } + private async processAcceptDiff(message: AcceptDiff) { + const context = this.triggerEventsStorage.getTriggerEvent((message.data as any)?.triggerID) || '' + this.editorContentController + .acceptDiff({ ...message, ...context }) + .then(() => { + this.telemetryHelper.recordInteractWithMessage(message) + }) + .catch((error) => { + this.telemetryHelper.recordInteractWithMessage(message, { result: 'Failed' }) + }) + } + + private async processViewDiff(message: ViewDiff) { + const context = this.triggerEventsStorage.getTriggerEvent((message.data as any)?.triggerID) || '' + this.editorContentController + .viewDiff({ ...message, ...context }) + .then(() => { + this.telemetryHelper.recordInteractWithMessage(message) + }) + .catch((error) => { + this.telemetryHelper.recordInteractWithMessage(message, { result: 'Failed' }) + }) + } + private async processCopyCodeToClipboard(message: CopyCodeToClipboard) { this.telemetryHelper.recordInteractWithMessage(message) } @@ -339,11 +378,20 @@ export class ChatController { this.editorContextExtractor .extractContextForTrigger('ContextMenu') - .then((context) => { + .then(async (context) => { const triggerID = randomUUID() + if (command.type === 'aws.amazonq.generateUnitTests') { + DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().publish({ + sender: 'testChat', + command: 'test', + type: 'chatMessage', + }) + // For non-supported languages, we'll just open the standard chat. + return + } if (context?.focusAreaContext?.codeBlock === undefined) { - throw 'Sorry, we cannot help with the selected language code snippet' + throw 'Sorry, I cannot help with the selected language code snippet' } const prompt = this.promptGenerator.generateForContextMenuCommand(command) @@ -503,7 +551,6 @@ export class ChatController { codeQuery: context?.focusAreaContext?.names, userIntent: this.userIntentRecognizer.getFromPromptChatMessage(message), customization: getSelectedCustomization(), - traceId: message.traceId, }, triggerID ) @@ -595,18 +642,20 @@ export class ChatController { this.messenger.sendOpenSettingsMessage(triggerID, tabID) return } - } - // if user does not have @workspace in the prompt, but user is in the data collection group - // If the user is in the data collection group but turned off local index to opt-out, do not collect data. - // TODO: Remove this entire block of code in one month as requested - else if ( - FeatureConfigProvider.instance.isAmznDataCollectionGroup() && + } else if ( !LspController.instance.isIndexingInProgress() && CodeWhispererSettings.instance.isLocalIndexEnabled() ) { - getLogger().info(`amazonq: User is in data collection group`) const start = performance.now() - triggerPayload.relevantTextDocuments = await LspController.instance.query(triggerPayload.message) + triggerPayload.relevantTextDocuments = await waitUntil( + async function () { + if (triggerPayload.message) { + return await LspController.instance.query(triggerPayload.message) + } + return [] + }, + { timeout: 500, interval: 200, truthy: false } + ) triggerPayload.projectContextQueryLatencyMs = performance.now() - start } } @@ -614,22 +663,35 @@ export class ChatController { const request = triggerPayloadToChatRequest(triggerPayload) const session = this.sessionStorage.getSession(tabID) getLogger().info( - `request from tab: ${tabID} conversationID: ${session.sessionIdentifier} request: ${JSON.stringify( - request - )}` + `request from tab: ${tabID} conversationID: ${session.sessionIdentifier} request: ${inspect(request, { + depth: 12, + })}` ) - let response: GenerateAssistantResponseCommandOutput | undefined = undefined + let response: MessengerResponseType | undefined = undefined session.createNewTokenSource() try { this.messenger.sendInitalStream(tabID, triggerID) - response = await session.chat(request) + this.telemetryHelper.setConversationStreamStartTime(tabID) + if (isSsoConnection(AuthUtil.instance.conn)) { + const { $metadata, generateAssistantResponseResponse } = await session.chatSso(request) + response = { + $metadata: $metadata, + message: generateAssistantResponseResponse, + } + } else { + const { $metadata, sendMessageResponse } = await session.chatIam(request as SendMessageRequest) + response = { + $metadata: $metadata, + message: sendMessageResponse, + } + } this.telemetryHelper.recordEnterFocusConversation(triggerEvent.tabID) this.telemetryHelper.recordStartConversation(triggerEvent, triggerPayload) getLogger().info( `response to tab: ${tabID} conversationID: ${session.sessionIdentifier} requestID: ${ response.$metadata.requestId - } metadata: ${JSON.stringify(response.$metadata)}` + } metadata: ${inspect(response.$metadata, { depth: 12 })}` ) await this.messenger.sendAIResponse(response, session, tabID, triggerID, triggerPayload) } catch (e: any) { diff --git a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts b/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts index 6bad96cd2fa..6604fd7bb21 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts @@ -13,9 +13,10 @@ import { QuickActionMessage, } from '../../../view/connector/connector' import { EditorContextCommandType } from '../../../commands/registerCommands' +import { ChatResponseStream as qdevChatResponseStream } from '@amzn/amazon-q-developer-streaming-client' import { + ChatResponseStream as cwChatResponseStream, CodeWhispererStreamingServiceException, - GenerateAssistantResponseCommandOutput, SupplementaryWebLink, } from '@amzn/codewhisperer-streaming' import { ChatMessage, ErrorMessage, FollowUp, Suggestion } from '../../../view/connector/connector' @@ -27,15 +28,21 @@ import { getHttpStatusCode, getRequestId, ToolkitError } from '../../../../share import { keys } from '../../../../shared/utilities/tsUtils' import { getLogger } from '../../../../shared/logger/logger' import { FeatureAuthState } from '../../../../codewhisperer/util/authUtil' -import { AuthFollowUpType, AuthMessageDataMap } from '../../../../amazonq/auth/model' -import { userGuideURL } from '../../../../amazonq/webview/ui/texts/constants' import { CodeScanIssue } from '../../../../codewhisperer/models/model' import { marked } from 'marked' import { JSDOM } from 'jsdom' import { LspController } from '../../../../amazonq/lsp/lspController' +import { extractCodeBlockLanguage } from '../../../../shared/markdown' +import { extractAuthFollowUp } from '../../../../amazonq/util/authUtils' +import { helpMessage } from '../../../../amazonq/webview/ui/texts/constants' export type StaticTextResponseType = 'quick-action-help' | 'onboarding-help' | 'transform' | 'help' +export type MessengerResponseType = { + $metadata: { requestId?: string; httpStatusCode?: number } + message?: AsyncIterable +} + export class Messenger { public constructor( private readonly dispatcher: AppToWebViewMessageDispatcher, @@ -43,26 +50,7 @@ export class Messenger { ) {} public async sendAuthNeededExceptionMessage(credentialState: FeatureAuthState, tabID: string, triggerID: string) { - let authType: AuthFollowUpType = 'full-auth' - let message = AuthMessageDataMap[authType].message - if ( - credentialState.codewhispererChat === 'disconnected' && - credentialState.codewhispererCore === 'disconnected' - ) { - authType = 'full-auth' - message = AuthMessageDataMap[authType].message - } - - if (credentialState.codewhispererCore === 'connected' && credentialState.codewhispererChat === 'expired') { - authType = 'missing_scopes' - message = AuthMessageDataMap[authType].message - } - - if (credentialState.codewhispererChat === 'expired' && credentialState.codewhispererCore === 'expired') { - authType = 're-auth' - message = AuthMessageDataMap[authType].message - } - + const { message, authType } = extractAuthFollowUp(credentialState) this.dispatcher.sendAuthNeededExceptionMessage( new AuthNeededException( { @@ -86,18 +74,29 @@ export class Messenger { relatedSuggestions: undefined, triggerID, messageID: '', + userIntent: undefined, + codeBlockLanguage: undefined, }, tabID ) ) } - + /** + * Tries to calculate the total number of code blocks. + * NOTES: + * - Not correct on all examples. Some may cause it to return 0 unexpectedly. + * - Plans in place (as of 4/22/2024) to move this server side. + * - See original pr: https://github.com/aws/aws-toolkit-vscode/pull/4761 for more details. + * @param message raw message response from codewhisperer client. + * @returns count of multi-line code blocks in response. + */ public async countTotalNumberOfCodeBlocks(message: string): Promise { + // TODO: remove this when moved to server-side. if (message === undefined) { return 0 } - // // To Convert Markdown text to HTML using marked library + // To Convert Markdown text to HTML using marked library const html = await marked(message) const dom = new JSDOM(html) @@ -110,7 +109,7 @@ export class Messenger { } public async sendAIResponse( - response: GenerateAssistantResponseCommandOutput, + response: MessengerResponseType, session: ChatSession, tabID: string, triggerID: string, @@ -121,8 +120,9 @@ export class Messenger { let codeReference: CodeReference[] = [] let followUps: FollowUp[] = [] let relatedSuggestions: Suggestion[] = [] + let codeBlockLanguage: string = 'plaintext' - if (response.generateAssistantResponseResponse === undefined) { + if (response.message === undefined) { throw new ToolkitError( `Empty response from CodeWhisperer Streaming service. Request ID: ${response.$metadata.requestId}` ) @@ -139,7 +139,7 @@ export class Messenger { const eventCounts = new Map() waitUntil( async () => { - for await (const chatEvent of response.generateAssistantResponseResponse!) { + for await (const chatEvent of response.message!) { for (const key of keys(chatEvent)) { if ((chatEvent[key] as any) !== undefined) { eventCounts.set(key, (eventCounts.get(key) ?? 0) + 1) @@ -172,7 +172,9 @@ export class Messenger { chatEvent.assistantResponseEvent.content.length > 0 ) { message += chatEvent.assistantResponseEvent.content - + if (codeBlockLanguage === 'plaintext') { + codeBlockLanguage = extractCodeBlockLanguage(message) + } this.dispatcher.sendChatMessage( new ChatMessage( { @@ -184,6 +186,8 @@ export class Messenger { codeReference, triggerID, messageID, + userIntent: triggerPayload.userIntent, + codeBlockLanguage: codeBlockLanguage, }, tabID ) @@ -260,6 +264,8 @@ export class Messenger { relatedSuggestions: undefined, triggerID, messageID, + userIntent: triggerPayload.userIntent, + codeBlockLanguage: codeBlockLanguage, }, tabID ) @@ -277,6 +283,8 @@ export class Messenger { relatedSuggestions, triggerID, messageID, + userIntent: triggerPayload.userIntent, + codeBlockLanguage: undefined, }, tabID ) @@ -293,7 +301,8 @@ export class Messenger { relatedSuggestions: undefined, triggerID, messageID, - traceId: triggerPayload.traceId, + userIntent: triggerPayload.userIntent, + codeBlockLanguage: undefined, }, tabID ) @@ -349,34 +358,7 @@ export class Messenger { let followUpsHeader switch (type) { case 'quick-action-help': - message = `I'm Amazon Q, a generative AI assistant. Learn more about me below. Your feedback will help me improve. - \n\n### What I can do: - \n\n- Answer questions about AWS - \n\n- Answer questions about general programming concepts - \n\n- Explain what a line of code or code function does - \n\n- Write unit tests and code - \n\n- Debug and fix code - \n\n- Refactor code - \n\n### What I don't do right now: - \n\n- Answer questions in languages other than English - \n\n- Remember conversations from your previous sessions - \n\n- Have information about your AWS account or your specific AWS resources - \n\n### Examples of questions I can answer: - \n\n- When should I use ElastiCache? - \n\n- How do I create an Application Load Balancer? - \n\n- Explain the and ask clarifying questions about it. - \n\n- What is the syntax of declaring a variable in TypeScript? - \n\n### Special Commands - \n\n- /clear - Clear the conversation. - \n\n- /dev - Get code suggestions across files in your current project. Provide a brief prompt, such as "Implement a GET API." - \n\n- /transform - Transform your code. Use to upgrade Java code versions. - \n\n- /help - View chat topics and commands. - \n\n### Things to note: - \n\n- I may not always provide completely accurate or current information. - \n\n- Provide feedback by choosing the like or dislike buttons that appear below answers. - \n\n- When you use Amazon Q, AWS may, for service improvement purposes, store data about your usage and content. You can opt-out of sharing this data by following the steps in AI services opt-out policies. See here - \n\n- Do not enter any confidential, sensitive, or personal information. - \n\n*For additional help, visit the [Amazon Q User Guide](${userGuideURL}).*` + message = helpMessage break case 'onboarding-help': message = `### What I can do: @@ -417,6 +399,8 @@ export class Messenger { relatedSuggestions: undefined, triggerID, messageID: 'static_message_' + triggerID, + userIntent: undefined, + codeBlockLanguage: undefined, }, tabID ) @@ -427,7 +411,7 @@ export class Messenger { let message = '' switch (quickAction) { case 'help': - message = 'What can Amazon Q help me with?' + message = 'How can Amazon Q help me?' break } diff --git a/packages/core/src/codewhispererChat/controllers/chat/model.ts b/packages/core/src/codewhispererChat/controllers/chat/model.ts index fdb8d9be7c1..f79510acacb 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/model.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/model.ts @@ -46,6 +46,7 @@ export interface InsertCodeAtCursorPosition { eventId: string codeBlockIndex: number totalCodeBlocks: number + codeBlockLanguage: string } export interface CopyCodeToClipboard { @@ -59,6 +60,32 @@ export interface CopyCodeToClipboard { eventId: string codeBlockIndex: number totalCodeBlocks: number + codeBlockLanguage: string +} + +export interface AcceptDiff { + command: string | undefined + tabID: string // rename tabId + messageId: string + actionId: string + data: string + code: string + referenceTrackerInformation?: CodeReference[] + eventId: string + codeBlockIndex?: number + totalCodeBlocks?: number +} +export interface ViewDiff { + command: string | undefined + tabID: string // rename tabId + messageId: string + actionId: string + data: string + code: string + referenceTrackerInformation?: CodeReference[] + eventId: string + codeBlockIndex?: number + totalCodeBlocks?: number } export type ChatPromptCommandType = @@ -72,7 +99,6 @@ export type ChatPromptCommandType = export interface PromptMessage { message: string | undefined messageId: string - traceId?: string command: ChatPromptCommandType | undefined userIntent: UserIntent | undefined tabID: string @@ -130,6 +156,7 @@ export interface ChatItemFeedbackMessage { export enum ChatTriggerType { ChatMessage = 'ChatMessage', + InlineChatMessage = 'InlineChatMessage', } export interface TriggerPayload { diff --git a/packages/core/src/codewhispererChat/controllers/chat/prompts/promptsGenerator.ts b/packages/core/src/codewhispererChat/controllers/chat/prompts/promptsGenerator.ts index d738eddcb67..6abecda2f8c 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/prompts/promptsGenerator.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/prompts/promptsGenerator.ts @@ -14,7 +14,6 @@ export class PromptsGenerator { ['aws.amazonq.fixCode', 'Fix'], ['aws.amazonq.optimizeCode', 'Optimize'], ['aws.amazonq.sendToPrompt', 'Send to prompt'], - ['aws.amazonq.generateUnitTests', 'Generate unit tests for'], ]) public generateForContextMenuCommand(command: EditorContextCommand): string { diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index 51ad0717412..feaeec75969 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts @@ -15,6 +15,8 @@ import { } from '../../../shared/telemetry/telemetry' import { ChatSessionStorage } from '../../storages/chatSession' import { + AcceptDiff, + ViewDiff, ChatItemFeedbackMessage, ChatItemVotedMessage, CopyCodeToClipboard, @@ -58,18 +60,49 @@ export function recordTelemetryChatRunCommand(type: CwsprChatCommandType, comman } export class CWCTelemetryHelper { + static instance: CWCTelemetryHelper private sessionStorage: ChatSessionStorage private triggerEventsStorage: TriggerEventsStorage private responseStreamStartTime: Map = new Map() private responseStreamTotalTime: Map = new Map() + private conversationStreamStartTime: Map = new Map() + private conversationStreamTotalTime: Map = new Map() private responseStreamTimeForChunks: Map = new Map() private responseWithProjectContext: Map = new Map() + // Keeps track of when chunks of data were displayed in a tab + private displayTimeForChunks: Map = new Map() + + /** + * Stores payload information about a message response until + * the full round trip time finishes and addMessage telemetry + * is sent + */ + private messageStorage: Map< + string, + { + triggerPayload: TriggerPayload + message: PromptAnswer + } + > = new Map() + constructor(sessionStorage: ChatSessionStorage, triggerEventsStorage: TriggerEventsStorage) { this.sessionStorage = sessionStorage this.triggerEventsStorage = triggerEventsStorage } + public static init(sessionStorage: ChatSessionStorage, triggerEventsStorage: TriggerEventsStorage) { + const lastInstance = CWCTelemetryHelper.instance + if (lastInstance !== undefined) { + return lastInstance + } + + getLogger().debug('CWCTelemetryHelper: Initialized new telemetry helper') + const instance = new CWCTelemetryHelper(sessionStorage, triggerEventsStorage) + CWCTelemetryHelper.instance = instance + return instance + } + private getUserIntentForTelemetry(userIntent: UserIntent | undefined): CwsprChatUserIntent | undefined { switch (userIntent) { case UserIntent.EXPLAIN_CODE_SELECTION: @@ -142,6 +175,7 @@ export class CWCTelemetryHelper { public recordInteractWithMessage( message: + | AcceptDiff | InsertCodeAtCursorPosition | CopyCodeToClipboard | PromptMessage @@ -149,6 +183,8 @@ export class CWCTelemetryHelper { | SourceLinkClickMessage | ResponseBodyLinkClickMessage | FooterInfoLinkClick + | ViewDiff, + { result }: { result: Result } = { result: 'Succeeded' } ) { const conversationId = this.getConversationId(message.tabID) let event: AmazonqInteractWithMessage | undefined @@ -156,7 +192,7 @@ export class CWCTelemetryHelper { case 'insert_code_at_cursor_position': message = message as InsertCodeAtCursorPosition event = { - result: 'Succeeded', + result, cwsprChatConversationId: conversationId ?? '', credentialStartUrl: AuthUtil.instance.startUrl, cwsprChatMessageId: message.messageId, @@ -169,12 +205,13 @@ export class CWCTelemetryHelper { cwsprChatCodeBlockIndex: message.codeBlockIndex, cwsprChatTotalCodeBlocks: message.totalCodeBlocks, cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId), + cwsprChatProgrammingLanguage: message.codeBlockLanguage, } break case 'code_was_copied_to_clipboard': message = message as CopyCodeToClipboard event = { - result: 'Succeeded', + result, cwsprChatConversationId: conversationId ?? '', credentialStartUrl: AuthUtil.instance.startUrl, cwsprChatMessageId: message.messageId, @@ -186,12 +223,45 @@ export class CWCTelemetryHelper { cwsprChatCodeBlockIndex: message.codeBlockIndex, cwsprChatTotalCodeBlocks: message.totalCodeBlocks, cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId), + cwsprChatProgrammingLanguage: message.codeBlockLanguage, + } + break + case 'accept_diff': + message = message as AcceptDiff + event = { + result, + cwsprChatConversationId: conversationId ?? '', + cwsprChatMessageId: message.messageId, + cwsprChatInteractionType: 'acceptDiff', + credentialStartUrl: AuthUtil.instance.startUrl, + cwsprChatAcceptedCharactersLength: message.code.length, + cwsprChatHasReference: + message.referenceTrackerInformation && message.referenceTrackerInformation.length > 0, + cwsprChatCodeBlockIndex: message.codeBlockIndex, + cwsprChatTotalCodeBlocks: message.totalCodeBlocks, + cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId), + } + break + case 'view_diff': + message = message as ViewDiff + event = { + result, + cwsprChatConversationId: conversationId ?? '', + cwsprChatMessageId: message.messageId, + cwsprChatInteractionType: 'viewDiff', + credentialStartUrl: AuthUtil.instance.startUrl, + cwsprChatAcceptedCharactersLength: message.code.length, + cwsprChatHasReference: + message.referenceTrackerInformation && message.referenceTrackerInformation.length > 0, + cwsprChatCodeBlockIndex: message.codeBlockIndex, + cwsprChatTotalCodeBlocks: message.totalCodeBlocks, + cwsprChatHasProjectContext: this.responseWithProjectContext.get(message.messageId), } break case 'follow-up-was-clicked': message = message as PromptMessage event = { - result: 'Succeeded', + result, cwsprChatConversationId: conversationId ?? '', credentialStartUrl: AuthUtil.instance.startUrl, cwsprChatMessageId: message.messageId, @@ -202,7 +272,7 @@ export class CWCTelemetryHelper { case 'chat-item-voted': message = message as ChatItemVotedMessage event = { - result: 'Succeeded', + result, cwsprChatMessageId: message.messageId, cwsprChatConversationId: conversationId ?? '', credentialStartUrl: AuthUtil.instance.startUrl, @@ -213,7 +283,7 @@ export class CWCTelemetryHelper { case 'source-link-click': message = message as SourceLinkClickMessage event = { - result: 'Succeeded', + result, cwsprChatMessageId: message.messageId, cwsprChatConversationId: conversationId ?? '', credentialStartUrl: AuthUtil.instance.startUrl, @@ -225,7 +295,7 @@ export class CWCTelemetryHelper { case 'response-body-link-click': message = message as ResponseBodyLinkClickMessage event = { - result: 'Succeeded', + result, cwsprChatMessageId: message.messageId, cwsprChatConversationId: conversationId ?? '', credentialStartUrl: AuthUtil.instance.startUrl, @@ -237,7 +307,7 @@ export class CWCTelemetryHelper { case 'footer-info-link-click': message = message as FooterInfoLinkClick event = { - result: 'Succeeded', + result, cwsprChatMessageId: 'footer', cwsprChatConversationId: conversationId ?? '', credentialStartUrl: AuthUtil.instance.startUrl, @@ -288,6 +358,10 @@ export class CWCTelemetryHelper { return 'UPVOTE' case 'downvote': return 'DOWNVOTE' + case 'acceptDiff': + return 'ACCEPT_DIFF' + case 'viewDiff': + return 'VIEW_DIFF' default: return 'UNKNOWN' } @@ -334,7 +408,26 @@ export class CWCTelemetryHelper { }) } + /** + * Store the trigger payload and message until the full message round trip finishes + * + * @calls emitAddMessage when the full message round trip finishes + */ public recordAddMessage(triggerPayload: TriggerPayload, message: PromptAnswer) { + this.messageStorage.set(message.tabID, { + triggerPayload, + message, + }) + } + + public emitAddMessage(tabID: string, fullDisplayLatency: number, traceId: string, startTime?: number) { + const payload = this.messageStorage.get(tabID) + if (!payload) { + return + } + + const { triggerPayload, message } = payload + const triggerEvent = this.triggerEventsStorage.getLastTriggerEventByTabID(message.tabID) const hasProjectLevelContext = triggerPayload.relevantTextDocuments && @@ -356,14 +449,22 @@ export class CWCTelemetryHelper { cwsprChatReferencesCount: message.codeReferenceCount, cwsprChatFollowUpCount: message.followUpCount, cwsprChatTimeToFirstChunk: this.getResponseStreamTimeToFirstChunk(message.tabID), - cwsprChatTimeBetweenChunks: JSON.stringify(this.getResponseStreamTimeBetweenChunks(message.tabID)), + cwsprChatTimeBetweenChunks: JSON.stringify( + this.getTimeBetweenChunks(message.tabID, this.responseStreamTimeForChunks) + ), cwsprChatFullResponseLatency: this.responseStreamTotalTime.get(message.tabID) ?? 0, + cwsprChatTimeToFirstDisplay: this.getFirstDisplayTime(tabID, startTime), + cwsprChatTimeToFirstUsableChunk: this.getFirstUsableChunkTime(message.tabID) ?? 0, + cwsprChatFullServerResponseLatency: this.conversationStreamTotalTime.get(message.tabID) ?? 0, + cwsprChatTimeBetweenDisplays: JSON.stringify(this.getTimeBetweenChunks(tabID, this.displayTimeForChunks)), + cwsprChatFullDisplayLatency: fullDisplayLatency, cwsprChatRequestLength: triggerPayload.message?.length ?? 0, cwsprChatResponseLength: message.messageLength, cwsprChatConversationType: 'Chat', credentialStartUrl: AuthUtil.instance.startUrl, codewhispererCustomizationArn: triggerPayload.customization.arn, cwsprChatHasProjectContext: hasProjectLevelContext, + traceId, } telemetry.amazonq_addMessage.emit(event) @@ -382,7 +483,7 @@ export class CWCTelemetryHelper { ...(language !== undefined ? { programmingLanguage: language } : {}), activeEditorTotalCharacters: event.cwsprChatActiveEditorTotalCharacters, timeToFirstChunkMilliseconds: event.cwsprChatTimeToFirstChunk, - timeBetweenChunks: this.getResponseStreamTimeBetweenChunks(message.tabID), + timeBetweenChunks: this.getTimeBetweenChunks(message.tabID, this.responseStreamTimeForChunks), fullResponselatency: event.cwsprChatFullResponseLatency, requestLength: event.cwsprChatRequestLength, responseLength: event.cwsprChatResponseLength, @@ -394,6 +495,8 @@ export class CWCTelemetryHelper { }) .then() .catch(logSendTelemetryEventFailure) + + this.messageStorage.delete(tabID) } public recordMessageResponseError(triggerPayload: TriggerPayload, tabID: string, responseCode: number) { @@ -438,6 +541,7 @@ export class CWCTelemetryHelper { public setResponseStreamStartTime(tabID: string) { this.responseStreamStartTime.set(tabID, performance.now()) this.responseStreamTimeForChunks.set(tabID, [performance.now()]) + this.displayTimeForChunks.set(tabID, []) } public setResponseStreamTimeForChunks(tabID: string) { @@ -445,10 +549,19 @@ export class CWCTelemetryHelper { this.responseStreamTimeForChunks.set(tabID, [...chunkTimes, performance.now()]) } + public setDisplayTimeForChunks(tabID: string, time: number) { + const chunkTimes = this.displayTimeForChunks.get(tabID) ?? [] + this.displayTimeForChunks.set(tabID, [...chunkTimes, time]) + } + public setResponseFromProjectContext(messageId: string) { this.responseWithProjectContext.set(messageId, true) } + public setConversationStreamStartTime(tabID: string) { + this.conversationStreamStartTime.set(tabID, performance.now()) + } + private getResponseStreamTimeToFirstChunk(tabID: string): number { const chunkTimes = this.responseStreamTimeForChunks.get(tabID) ?? [0, 0] if (chunkTimes.length === 1) { @@ -457,10 +570,28 @@ export class CWCTelemetryHelper { return Math.round(chunkTimes[1] - chunkTimes[0]) } - private getResponseStreamTimeBetweenChunks(tabID: string): number[] { + /** + * Finds the time between when a user pressed enter and the first chunk appears in the UI + */ + private getFirstDisplayTime(tabID: string, startTime?: number) { + if (!startTime) { + return 0 + } + const chunkTimes = this.displayTimeForChunks.get(tabID) ?? [0] + return Math.round(chunkTimes[0] - startTime) + } + + private getFirstUsableChunkTime(tabID: string) { + const startTime = this.conversationStreamStartTime.get(tabID) ?? 0 + const chunkTimes = this.responseStreamTimeForChunks.get(tabID) ?? [0, 0] + // first chunk is the start time, we use the second because thats the first actual usable chunk time + return Math.round(chunkTimes[1] - startTime) + } + + private getTimeBetweenChunks(tabID: string, chunks: Map): number[] { try { const chunkDeltaTimes: number[] = [] - const chunkTimes = this.responseStreamTimeForChunks.get(tabID) ?? [0] + const chunkTimes = chunks.get(tabID) ?? [0] for (let idx = 0; idx < chunkTimes.length - 1; idx++) { chunkDeltaTimes.push(Math.round(chunkTimes[idx + 1] - chunkTimes[idx])) } @@ -473,8 +604,13 @@ export class CWCTelemetryHelper { } public setResponseStreamTotalTime(tabID: string) { - const totalTime = performance.now() - (this.responseStreamStartTime.get(tabID) ?? 0) - this.responseStreamTotalTime.set(tabID, Math.round(totalTime)) + // time from when the requests started streaming until the end of the stream + const totalStreamingTime = performance.now() - (this.responseStreamStartTime.get(tabID) ?? 0) + this.responseStreamTotalTime.set(tabID, Math.round(totalStreamingTime)) + + // time from the initial server request, including creating the conversation id, until the end of the stream + const totalConversationTime = performance.now() - (this.conversationStreamStartTime.get(tabID) ?? 0) + this.conversationStreamTotalTime.set(tabID, Math.round(totalConversationTime)) } public getConversationId(tabID: string): string | undefined { diff --git a/packages/core/src/codewhispererChat/controllers/chat/userIntent/userIntentRecognizer.ts b/packages/core/src/codewhispererChat/controllers/chat/userIntent/userIntentRecognizer.ts index 058f5e8effa..32b6b274a51 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/userIntent/userIntentRecognizer.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/userIntent/userIntentRecognizer.ts @@ -6,6 +6,7 @@ import { UserIntent } from '@amzn/codewhisperer-streaming' import { EditorContextCommand } from '../../../commands/registerCommands' import { PromptMessage } from '../model' +import { Auth } from '../../../../auth' export class UserIntentRecognizer { public getFromContextMenuCommand(command: EditorContextCommand): UserIntent | undefined { @@ -38,6 +39,8 @@ export class UserIntentRecognizer { return UserIntent.APPLY_COMMON_BEST_PRACTICES } else if (prompt.message.startsWith('Optimize')) { return UserIntent.IMPROVE_CODE + } else if (prompt.message.startsWith('Generate unit tests') && Auth.instance.isInternalAmazonUser()) { + return UserIntent.GENERATE_UNIT_TESTS } return undefined } diff --git a/packages/core/src/codewhispererChat/editor/context/file/javaImportReader.ts b/packages/core/src/codewhispererChat/editor/context/file/javaImportReader.ts index 332829868a4..b66a6c44936 100644 --- a/packages/core/src/codewhispererChat/editor/context/file/javaImportReader.ts +++ b/packages/core/src/codewhispererChat/editor/context/file/javaImportReader.ts @@ -24,13 +24,13 @@ export function extractContextFromJavaImports(names: any): string[] { if (commonJavaImportsPrefixesRegex.test(importStatement)) { return '' } else if (importStatement.startsWith(awsJavaSdkV1Prefix)) { - //@ts-ignore + // @ts-ignore return javaImport.packages?.at(1) ?? '' } else if (importStatement.startsWith(awsJavaSdkV2Prefix)) { - //@ts-ignore + // @ts-ignore return javaImport.packages?.at(2) ?? '' } else { - //@ts-ignore + // @ts-ignore return javaImport.packages?.at(0) ?? javaImport.organisation ?? javaImport.tld } }) diff --git a/packages/core/src/codewhispererChat/editor/context/file/languages.ts b/packages/core/src/codewhispererChat/editor/context/file/languages.ts index b2dac49f2ad..51887eaf31b 100644 --- a/packages/core/src/codewhispererChat/editor/context/file/languages.ts +++ b/packages/core/src/codewhispererChat/editor/context/file/languages.ts @@ -5,57 +5,57 @@ import { TextDocument } from 'vscode' +const defaultLanguages = [ + 'yaml', + 'xsl', + 'xml', + 'vue', + 'tex', + 'typescript', + 'swift', + 'stylus', + 'sql', + 'slim', + 'shaderlab', + 'sass', + 'rust', + 'ruby', + 'r', + 'python', + 'pug', + 'powershell', + 'php', + 'perl', + 'markdown', + 'makefile', + 'lua', + 'less', + 'latex', + 'json', + 'javascript', + 'java', + 'ini', + 'html', + 'haml', + 'handlebars', + 'groovy', + 'go', + 'diff', + 'css', + 'c', + 'coffeescript', + 'clojure', + 'bibtex', + 'abap', +] + export function extractLanguageNameFromFile(file: TextDocument): string | undefined { const languageId = file.languageId if (languageId === undefined) { return undefined } - if ( - [ - 'yaml', - 'xsl', - 'xml', - 'vue', - 'tex', - 'typescript', - 'swift', - 'stylus', - 'sql', - 'slim', - 'shaderlab', - 'sass', - 'rust', - 'ruby', - 'r', - 'python', - 'pug', - 'powershell', - 'php', - 'perl', - 'markdown', - 'makefile', - 'lua', - 'less', - 'latex', - 'json', - 'javascript', - 'java', - 'ini', - 'html', - 'haml', - 'handlebars', - 'groovy', - 'go', - 'diff', - 'css', - 'c', - 'coffeescript', - 'clojure', - 'bibtex', - 'abap', - ].includes(languageId) - ) { + if (defaultLanguages.includes(languageId)) { return languageId } switch (languageId) { @@ -117,54 +117,7 @@ export function extractLanguageNameFromFile(file: TextDocument): string | undefi export function extractAdditionalLanguageMatchPoliciesFromFile(file: TextDocument): Set { const languageId = file.languageId - if (languageId === undefined) { - return new Set() - } - if ( - [ - 'yaml', - 'xsl', - 'xml', - 'vue', - 'tex', - 'typescript', - 'swift', - 'stylus', - 'sql', - 'slim', - 'shaderlab', - 'sass', - 'rust', - 'ruby', - 'r', - 'python', - 'pug', - 'powershell', - 'php', - 'perl', - 'markdown', - 'makefile', - 'lua', - 'less', - 'latex', - 'json', - 'javascript', - 'java', - 'ini', - 'html', - 'haml', - 'handlebars', - 'groovy', - 'go', - 'diff', - 'css', - 'c', - 'coffeescript', - 'clojure', - 'bibtex', - 'abap', - ].includes(languageId) - ) { + if (languageId === undefined || defaultLanguages.includes(languageId)) { return new Set() } switch (languageId) { diff --git a/packages/core/src/codewhispererChat/index.ts b/packages/core/src/codewhispererChat/index.ts index 0a954458cfe..e473203caf5 100644 --- a/packages/core/src/codewhispererChat/index.ts +++ b/packages/core/src/codewhispererChat/index.ts @@ -7,3 +7,11 @@ export { FocusAreaContextExtractor } from './editor/context/focusArea/focusAreaE export { TryChatCodeLensProvider, resolveModifierKey, tryChatCodeLensCommand } from './editor/codelens' export { focusAmazonQPanel } from './commands/registerCommands' export { ChatSession } from './clients/chat/v0/chat' +export { triggerPayloadToChatRequest } from './controllers/chat/chatRequest/converter' +export { ChatTriggerType, PromptMessage, TriggerPayload } from './controllers/chat/model' +export { UserIntentRecognizer } from './controllers/chat/userIntent/userIntentRecognizer' +export { EditorContextExtractor } from './editor/context/extractor' +export { ChatSessionStorage } from './storages/chatSession' +export { TriggerEventsStorage } from './storages/triggerEvents' +export { ReferenceLogController } from './view/messages/referenceLogController' +export { extractLanguageNameFromFile } from './editor/context/file/languages' diff --git a/packages/core/src/codewhispererChat/storages/triggerEvents.ts b/packages/core/src/codewhispererChat/storages/triggerEvents.ts index 2218d4a11a9..d30ebf48939 100644 --- a/packages/core/src/codewhispererChat/storages/triggerEvents.ts +++ b/packages/core/src/codewhispererChat/storages/triggerEvents.ts @@ -12,6 +12,7 @@ export type TriggerEventType = | 'follow_up' | 'onboarding_page_interaction' | 'quick_action' + | 'inline_chat' export interface TriggerEvent { readonly id: string diff --git a/packages/core/src/codewhispererChat/view/connector/connector.ts b/packages/core/src/codewhispererChat/view/connector/connector.ts index cabbd23b9b2..02794af5fb3 100644 --- a/packages/core/src/codewhispererChat/view/connector/connector.ts +++ b/packages/core/src/codewhispererChat/view/connector/connector.ts @@ -141,7 +141,8 @@ export interface ChatMessageProps { readonly codeReference?: CodeReference[] readonly triggerID: string readonly messageID: string - readonly traceId?: string + readonly userIntent: string | undefined + readonly codeBlockLanguage: string | undefined } export class ChatMessage extends UiMessage { @@ -154,7 +155,8 @@ export class ChatMessage extends UiMessage { readonly followUpsHeader: string | undefined readonly triggerID: string readonly messageID: string | undefined - readonly traceId?: string + readonly userIntent: string | undefined + readonly codeBlockLanguage: string | undefined override type = 'chatMessage' constructor(props: ChatMessageProps, tabID: string) { @@ -167,7 +169,8 @@ export class ChatMessage extends UiMessage { this.codeReference = props.codeReference this.triggerID = props.triggerID this.messageID = props.messageID - this.traceId = props.traceId + this.userIntent = props.userIntent + this.codeBlockLanguage = props.codeBlockLanguage } } diff --git a/packages/core/src/codewhispererChat/view/messages/messageListener.ts b/packages/core/src/codewhispererChat/view/messages/messageListener.ts index 266eaf8d4af..93c750ab01b 100644 --- a/packages/core/src/codewhispererChat/view/messages/messageListener.ts +++ b/packages/core/src/codewhispererChat/view/messages/messageListener.ts @@ -63,6 +63,12 @@ export class UIMessageListener { }) } break + case 'accept_diff': + this.processAcceptDiff(msg) + break + case 'view_diff': + this.processViewDiff(msg) + break case 'code_was_copied_to_clipboard': this.processCodeWasCopiedToClipboard(msg) break @@ -159,6 +165,23 @@ export class UIMessageListener { eventId: msg.eventId, codeBlockIndex: msg.codeBlockIndex, totalCodeBlocks: msg.totalCodeBlocks, + codeBlockLanguage: msg.codeBlockLanguage, + }) + } + + private processAcceptDiff(msg: any) { + this.chatControllerMessagePublishers.processAcceptDiff.publish({ + command: msg.command, + tabID: msg.tabID || msg.tabId, + ...msg, + }) + } + + private processViewDiff(msg: any) { + this.chatControllerMessagePublishers.processViewDiff.publish({ + command: msg.command, + tabID: msg.tabID || msg.tabId, + ...msg, }) } @@ -174,6 +197,7 @@ export class UIMessageListener { eventId: msg.eventId, codeBlockIndex: msg.codeBlockIndex, totalCodeBlocks: msg.totalCodeBlocks, + codeBlockLanguage: msg.codeBlockLanguage, }) } @@ -204,7 +228,6 @@ export class UIMessageListener { tabID: msg.tabID, messageId: msg.messageId, userIntent: msg.userIntent !== '' ? msg.userIntent : undefined, - traceId: msg.traceId, }) } diff --git a/packages/core/src/dev/activation.ts b/packages/core/src/dev/activation.ts index fac6cbeb11c..b4100b191ce 100644 --- a/packages/core/src/dev/activation.ts +++ b/packages/core/src/dev/activation.ts @@ -4,7 +4,6 @@ */ import * as vscode from 'vscode' -import * as config from './config' import { createCommonButtons } from '../shared/ui/buttons' import { createQuickPick } from '../shared/ui/pickerPrompter' import { SkipPrompter } from '../shared/ui/common/skipPrompter' @@ -14,9 +13,6 @@ import { Commands } from '../shared/vscode/commands2' import { createInputBox } from '../shared/ui/inputPrompter' import { Wizard } from '../shared/wizards/wizard' import { deleteDevEnvCommand, installVsixCommand, openTerminalCommand } from './codecatalyst' -import { watchBetaVSIX } from './beta' -import { isCloud9 } from '../shared/extensionUtilities' -import { isReleaseVersion } from '../shared/vscode/env' import { isAnySsoConnection } from '../auth/connection' import { Auth } from '../auth/auth' import { getLogger } from '../shared/logger' @@ -24,6 +20,10 @@ import { entries } from '../shared/utilities/tsUtils' import { getEnvironmentSpecificMemento } from '../shared/utilities/mementos' import { setContext } from '../shared' import { telemetry } from '../shared/telemetry' +import { getSessionId } from '../shared/telemetry/util' +import { NotificationsController } from '../notifications/controller' +import { DevNotificationsState } from '../notifications/types' +import { QuickPickItem } from 'vscode' interface MenuOption { readonly label: string @@ -37,20 +37,25 @@ export type DevFunction = | 'openTerminal' | 'deleteDevEnv' | 'editStorage' + | 'resetState' | 'showEnvVars' | 'deleteSsoConnections' | 'expireSsoConnections' | 'editAuthConnections' + | 'notificationsSend' + | 'forceIdeCrash' export type DevOptions = { context: vscode.ExtensionContext - auth: Auth + auth: () => Auth + notificationsController: () => NotificationsController menuOptions?: DevFunction[] } let targetContext: vscode.ExtensionContext let globalState: vscode.Memento let targetAuth: Auth +let targetNotificationsController: NotificationsController /** * Defines AWS Toolkit developer tools. @@ -59,52 +64,69 @@ let targetAuth: Auth * on selection. There is no support for name-spacing. Just add the relevant * feature/module as a description so it can be moved around easier. */ -const menuOptions: Record = { - installVsix: { - label: 'Install VSIX on Remote Environment', - description: 'CodeCatalyst', - detail: 'Automatically upload/install a VSIX to a remote host', - executor: installVsixCommand, - }, - openTerminal: { - label: 'Open Remote Terminal', - description: 'CodeCatalyst', - detail: 'Opens a new terminal connected to the remote environment', - executor: openTerminalCommand, - }, - deleteDevEnv: { - label: 'Delete Workspace', - description: 'CodeCatalyst', - detail: 'Deletes the selected Dev Environment', - executor: deleteDevEnvCommand, - }, - editStorage: { - label: 'Show or Edit globalState', - description: 'VS Code', - detail: 'Shows all globalState values, or edit a globalState/secret item', - executor: openStorageFromInput, - }, - showEnvVars: { - label: 'Show Environment Variables', - description: 'AWS Toolkit', - detail: 'Shows all environment variable values', - executor: () => showState('envvars'), - }, - deleteSsoConnections: { - label: 'Auth: Delete SSO Connections', - detail: 'Deletes all SSO Connections the extension is using.', - executor: deleteSsoConnections, - }, - expireSsoConnections: { - label: 'Auth: Expire SSO Connections', - detail: 'Force expires all SSO Connections, in to a "needs reauthentication" state.', - executor: expireSsoConnections, - }, - editAuthConnections: { - label: 'Auth: Edit Connections', - detail: 'Opens editor to all Auth Connections the extension is using.', - executor: editSsoConnections, - }, +const menuOptions: () => Record = () => { + return { + installVsix: { + label: 'Install VSIX on Remote Environment', + description: 'CodeCatalyst', + detail: 'Automatically upload/install a VSIX to a remote host', + executor: installVsixCommand, + }, + openTerminal: { + label: 'Open Remote Terminal', + description: 'CodeCatalyst', + detail: 'Opens a new terminal connected to the remote environment', + executor: openTerminalCommand, + }, + deleteDevEnv: { + label: 'Delete Workspace', + description: 'CodeCatalyst', + detail: 'Deletes the selected Dev Environment', + executor: deleteDevEnvCommand, + }, + editStorage: { + label: 'Show or Edit globalState', + description: 'VS Code', + detail: 'Shows all globalState values, or edit a globalState/secret item', + executor: openStorageFromInput, + }, + resetState: { + label: 'Reset feature state', + detail: 'Quick reset the state of extension components or features', + executor: resetState, + }, + showEnvVars: { + label: 'Show Environment Variables', + description: 'AWS Toolkit', + detail: 'Shows all environment variable values', + executor: () => showState('envvars'), + }, + deleteSsoConnections: { + label: 'Auth: Delete SSO Connections', + detail: 'Deletes all SSO Connections the extension is using.', + executor: deleteSsoConnections, + }, + expireSsoConnections: { + label: 'Auth: Expire SSO Connections', + detail: 'Force expires all SSO Connections, in to a "needs reauthentication" state.', + executor: expireSsoConnections, + }, + editAuthConnections: { + label: 'Auth: Edit Connections', + detail: 'Opens editor to all Auth Connections the extension is using.', + executor: editSsoConnections, + }, + notificationsSend: { + label: 'Notifications: Send Notifications', + detail: 'Send JSON notifications for testing.', + executor: editNotifications, + }, + forceIdeCrash: { + label: 'Crash: Force IDE ExtHost Crash', + detail: `Will SIGKILL ExtHost, { pid: ${process.pid}, sessionId: '${getSessionId().slice(0, 8)}-...' }, but the IDE itself will not crash.`, + executor: forceQuitIde, + }, + } } /** @@ -151,17 +173,23 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.workspace.registerTextDocumentContentProvider('aws-dev2', new DevDocumentProvider()), // "AWS (Developer): Open Developer Menu" vscode.commands.registerCommand('aws.dev.openMenu', async () => { - await vscode.commands.executeCommand('_aws.dev.invokeMenu', { context: ctx, auth: Auth.instance }) + await vscode.commands.executeCommand('_aws.dev.invokeMenu', { + context: ctx, + auth: () => Auth.instance, + notificationsController: () => NotificationsController.instance, + }) }), // Internal command to open dev menu for a specific context and options vscode.commands.registerCommand('_aws.dev.invokeMenu', (opts: DevOptions) => { targetContext = opts.context // eslint-disable-next-line aws-toolkits/no-banned-usages globalState = targetContext.globalState - targetAuth = opts.auth + targetAuth = opts.auth() + targetNotificationsController = opts.notificationsController() + const options = menuOptions() void openMenu( - entries(menuOptions) - .filter((e) => (opts.menuOptions ?? Object.keys(menuOptions)).includes(e[0])) + entries(options) + .filter((e) => (opts.menuOptions ?? Object.keys(options)).includes(e[0])) .map((e) => e[1]) ) }), @@ -185,10 +213,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { const editor = new ObjectEditor() ctx.subscriptions.push(openStorageCommand.register(editor)) - - if (!isCloud9() && !isReleaseVersion() && config.betaUrl) { - ctx.subscriptions.push(watchBetaVSIX(config.betaUrl)) - } } async function openMenu(options: MenuOption[]): Promise { @@ -216,6 +240,7 @@ function isSecrets(obj: vscode.Memento | vscode.SecretStorage): obj is vscode.Se class VirtualObjectFile implements FileProvider { private mTime = 0 + private size = 0 private readonly onDidChangeEmitter = new vscode.EventEmitter() public readonly onDidChange = this.onDidChangeEmitter.event @@ -227,8 +252,8 @@ class VirtualObjectFile implements FileProvider { /** Emits an event indicating this file's content has changed */ public refresh() { /** - * Per {@link vscode.FileSystemProvider.onDidChangeFile}, if the mTime does not change, new file content may - * not be retrieved. Without this, when we emit a change the text editor did not update. + * Per {@link vscode.FileSystemProvider.onDidChangeFile}, if the mTime and/or size does not change, new file content may + * not be retrieved due to optimizations. Without this, when we emit a change the text editor did not update. */ this.mTime++ this.onDidChangeEmitter.fire() @@ -236,13 +261,15 @@ class VirtualObjectFile implements FileProvider { public stat(): { ctime: number; mtime: number; size: number } { // This would need to be filled out to track conflicts - return { ctime: 0, mtime: this.mTime, size: 0 } + return { ctime: 0, mtime: this.mTime, size: this.size } } public async read(): Promise { const encoder = new TextEncoder() - return encoder.encode(await this.readStore(this.key)) + const data = encoder.encode(await this.readStore(this.key)) + this.size = data.length + return data } public async write(content: Uint8Array): Promise { @@ -297,7 +324,7 @@ class ObjectEditor { vscode.workspace.registerFileSystemProvider(ObjectEditor.scheme, this.fs) } - public async openStorage(type: 'globalsView' | 'globals' | 'secrets' | 'auth', key: string): Promise { + public async openStorage(type: 'globalsView' | 'globals' | 'secrets' | 'auth', key: string) { switch (type) { case 'globalsView': return showState('globalstate') @@ -311,17 +338,19 @@ class ObjectEditor { } } - private async openState(storage: vscode.Memento | vscode.SecretStorage, key: string): Promise { + private async openState(storage: vscode.Memento | vscode.SecretStorage, key: string) { const uri = this.uriFromKey(key, storage) const tab = this.tabs.get(this.fs.uriToKey(uri)) if (tab) { tab.virtualFile.refresh() await vscode.window.showTextDocument(tab.editor.document) + return tab.virtualFile } else { const newTab = await this.createTab(storage, key) const newKey = this.fs.uriToKey(newTab.editor.document.uri) this.tabs.set(newKey, newTab) + return newTab.virtualFile } } @@ -382,7 +411,7 @@ async function openStorageFromInput() { title: 'Enter a key', }) } else if (target === 'globalsView') { - return new SkipPrompter('') + return new SkipPrompter() } else if (target === 'globals') { // List all globalState keys in the quickpick menu. const items = globalState @@ -412,6 +441,62 @@ async function openStorageFromInput() { } } +type ResettableFeature = { + name: string + executor: () => Promise | void +} & QuickPickItem + +/** + * Extend this array with features that may need state resets often for + * testing purposes. It will appear as an entry in the "Reset feature state" menu. + */ +const resettableFeatures: readonly ResettableFeature[] = [ + { + name: 'notifications', + label: 'Notifications', + detail: 'Resets memory/global state for the notifications panel (includes dismissed, onReceive).', + executor: resetNotificationsState, + }, +] as const + +// TODO this is *somewhat* similar to `openStorageFromInput`. If we need another +// one of these prompters, can we make it generic? +async function resetState() { + const wizard = new (class extends Wizard<{ target: string; key: string }> { + constructor() { + super() + + this.form.target.bindPrompter(() => + createQuickPick( + resettableFeatures.map((f) => { + return { + data: f.name, + label: f.label, + detail: f.detail, + } + }), + { + title: 'Select a feature/component to reset', + } + ) + ) + + this.form.key.bindPrompter(({ target }) => { + if (target && resettableFeatures.some((f) => f.name === target)) { + return new SkipPrompter() + } + throw new Error('invalid feature target') + }) + } + })() + + const response = await wizard.run() + + if (response) { + return resettableFeatures.find((f) => f.name === response.target)?.executor() + } +} + async function editSsoConnections() { void openStorageCommand.execute('auth', 'auth.profiles') } @@ -435,6 +520,15 @@ async function expireSsoConnections() { ) } +export function forceQuitIde() { + // This current process is the ExtensionHost. Killing it will cause all the extensions to crash + // for the current ExtensionHost (unless using "extensions.experimental.affinity"). + // The IDE instance itself will remaing running, but a new ExtHost will spawn within it. + // The PPID (parent process) is vscode itself, killing it crashes all vscode instances. + const vsCodePid = process.pid + process.kill(vsCodePid, 'SIGKILL') // SIGTERM would be the graceful shutdown +} + async function showState(path: string) { const uri = vscode.Uri.parse(`aws-dev2://state/${path}-${targetContext.extension.id}`) const doc = await vscode.workspace.openTextDocument(uri) @@ -446,3 +540,41 @@ export const openStorageCommand = Commands.from(ObjectEditor).declareOpenStorage export async function updateDevMode() { await setContext('aws.isDevMode', DevSettings.instance.isDevMode()) } + +async function resetNotificationsState() { + await targetNotificationsController.reset() +} + +async function editNotifications() { + const storageKey = 'aws.notifications.dev' + const current = globalState.get(storageKey) ?? {} + const isValid = (item: any) => { + if (typeof item !== 'object' || !Array.isArray(item.startUp) || !Array.isArray(item.emergency)) { + return false + } + return true + } + if (!isValid(current)) { + // Set a default state if the developer does not have it or it's malformed. + await globalState.update(storageKey, { startUp: [], emergency: [] } as DevNotificationsState) + } + + // Monitor for when the global state is updated. + // A notification will be sent based on the contents. + const virtualFile = await openStorageCommand.execute('globals', storageKey) + virtualFile?.onDidChange(async () => { + const val = globalState.get(storageKey) as DevNotificationsState + if (!isValid(val)) { + void vscode.window.showErrorMessage( + 'Dev mode: invalid notification object provided. State data must take the form: { "startUp": ToolkitNotification[], "emergency": ToolkitNotification[] }' + ) + return + } + + // This relies on the controller being built with DevFetcher, as opposed to + // the default RemoteFetcher. DevFetcher will check for notifications in the + // global state, which was just modified. + await targetNotificationsController.pollForStartUp() + await targetNotificationsController.pollForEmergencies() + }) +} diff --git a/packages/core/src/dev/beta.ts b/packages/core/src/dev/beta.ts index 69935ace908..b09516dcc3c 100644 --- a/packages/core/src/dev/beta.ts +++ b/packages/core/src/dev/beta.ts @@ -18,10 +18,15 @@ import { isUserCancelledError, ToolkitError } from '../shared/errors' import { telemetry } from '../shared/telemetry/telemetry' import { cast } from '../shared/utilities/typeConstructors' import { CancellationError } from '../shared/utilities/timeoutUtils' +import { isAmazonQ, isCloud9, productName } from '../shared/extensionUtilities' +import * as devConfig from './config' +import { isReleaseVersion } from '../shared/vscode/env' +import { getRelativeDate } from '../shared/datetime' const localize = nls.loadMessageBundle() +const logger = getLogger('dev/beta') -const downloadIntervalMs = 1000 * 60 * 60 * 24 // A day in milliseconds +const downloadIntervalMs = 1000 * 60 * 60 * 3 // 3 hours (8 times/day). interface BetaToolkit { readonly needUpdate: boolean @@ -39,17 +44,28 @@ async function updateBetaToolkitData(vsixUrl: string, data: BetaToolkit) { }) } +/** + * Set up "beta" update monitoring. + */ +export async function activate(ctx: vscode.ExtensionContext) { + const betaUrl = isAmazonQ() ? devConfig.betaUrl.amazonq : devConfig.betaUrl.toolkit + if (!isCloud9() && !isReleaseVersion() && betaUrl) { + ctx.subscriptions.push(watchBetaVSIX(betaUrl)) + } +} + /** * Watch the beta VSIX daily for changes. * If this is the first time we are watching the beta version or if its been 24 hours since it was last checked then try to prompt for update */ export function watchBetaVSIX(vsixUrl: string): vscode.Disposable { - getLogger().info(`dev: watching ${vsixUrl} for beta artifacts`) - const toolkit = getBetaToolkitData(vsixUrl) + const lastCheckRel = toolkit ? getRelativeDate(new Date(toolkit.lastCheck)) : '' + logger.info('watching beta artifacts url (lastCheck: %s): %s', lastCheckRel, vsixUrl) + if (!toolkit || toolkit.needUpdate || Date.now() - toolkit.lastCheck > downloadIntervalMs) { runAutoUpdate(vsixUrl).catch((e) => { - getLogger().error('runAutoUpdate failed: %s', (e as Error).message) + logger.error('runAutoUpdate failed: %s', (e as Error).message) }) } @@ -58,13 +74,13 @@ export function watchBetaVSIX(vsixUrl: string): vscode.Disposable { } async function runAutoUpdate(vsixUrl: string) { - getLogger().debug(`dev: checking ${vsixUrl} for a new version`) + logger.debug(`checking url for a new version: %s`, vsixUrl) try { await telemetry.aws_autoUpdateBeta.run(() => checkBetaUrl(vsixUrl)) } catch (e) { if (!isUserCancelledError(e)) { - getLogger().warn(`dev: beta extension auto-update failed: %s`, e) + logger.warn('beta extension auto-update failed: %s', e) } } } @@ -75,11 +91,12 @@ async function runAutoUpdate(vsixUrl: string) { async function checkBetaUrl(vsixUrl: string): Promise { const resp = await got(vsixUrl).buffer() const latestBetaInfo = await getExtensionInfo(resp) - if (VSCODE_EXTENSION_ID.awstoolkit !== `${latestBetaInfo.publisher}.${latestBetaInfo.name}`) { + const extId = isAmazonQ() ? VSCODE_EXTENSION_ID.amazonq : VSCODE_EXTENSION_ID.awstoolkit + if (extId !== `${latestBetaInfo.publisher}.${latestBetaInfo.name}`) { throw new ToolkitError('URL does not point to an AWS Toolkit artifact', { code: 'InvalidExtensionName' }) } - const currentVersion = vscode.extensions.getExtension(VSCODE_EXTENSION_ID.awstoolkit)?.packageJSON.version + const currentVersion = vscode.extensions.getExtension(extId)?.packageJSON.version if (latestBetaInfo.version !== currentVersion) { const tmpFolder = await makeTemporaryToolkitFolder() const betaPath = vscode.Uri.joinPath(vscode.Uri.file(tmpFolder), path.basename(vsixUrl)) @@ -141,7 +158,8 @@ async function promptInstallToolkit(pluginPath: vscode.Uri, newVersion: string, const response = await vscode.window.showInformationMessage( localize( 'AWS.dev.beta.updatePrompt', - `New version of AWS Toolkit is available at the [beta URL]({0}). Install the new version "{1}" to continue using the beta.`, + 'New version of {0} is available at the [beta URL]({1}). Install the new version "{2}" to continue using the beta.', + productName(), vsixUrl, newVersion ), @@ -151,13 +169,15 @@ async function promptInstallToolkit(pluginPath: vscode.Uri, newVersion: string, switch (response) { case installBtn: try { - getLogger().info(`dev: installing artifact ${vsixName}`) + logger.info(`installing artifact: ${vsixName}`) await vscode.commands.executeCommand('workbench.extensions.installExtension', pluginPath) await updateBetaToolkitData(vsixUrl, { lastCheck: Date.now(), needUpdate: false, }) - reloadWindowPrompt(localize('AWS.dev.beta.reloadPrompt', 'Reload now to use the new beta AWS Toolkit.')) + reloadWindowPrompt( + localize('AWS.dev.beta.reloadPrompt', 'Reload now to use the new beta {0}.', productName()) + ) } catch (e) { throw ToolkitError.chain(e, `Failed to install ${vsixName}`, { code: 'FailedExtensionInstall' }) } diff --git a/packages/core/src/dev/codecatalyst.ts b/packages/core/src/dev/codecatalyst.ts index e4ee30188b8..2c575f71eac 100644 --- a/packages/core/src/dev/codecatalyst.ts +++ b/packages/core/src/dev/codecatalyst.ts @@ -4,7 +4,6 @@ */ import { glob } from 'glob' -import * as fs from 'fs-extra' import * as path from 'path' import * as vscode from 'vscode' import * as manifest from '../../package.json' @@ -21,6 +20,7 @@ import { startVscodeRemote } from '../shared/extensions/ssh' import { isValidResponse } from '../shared/wizards/wizard' import { createQuickPick } from '../shared/ui/pickerPrompter' import { createCommonButtons } from '../shared/ui/buttons' +import { fs } from '../shared' type LazyProgress = vscode.Progress & vscode.Disposable & { getToken(): Timeout } @@ -217,7 +217,7 @@ async function installVsix( if (path.extname(resp) !== '.vsix') { progress.report({ message: 'Copying extension...' }) - const packageData = await fs.readFile(path.join(resp, 'package.json'), 'utf-8') + const packageData = await fs.readFileText(path.join(resp, 'package.json')) const targetManfiest: typeof manifest = JSON.parse(packageData) const destName = `${extPath}/${extId}-${targetManfiest.version}` const source = `${resp}${path.sep}` diff --git a/packages/core/src/dev/config.ts b/packages/core/src/dev/config.ts index 219f9b35fec..d5fa49b2426 100644 --- a/packages/core/src/dev/config.ts +++ b/packages/core/src/dev/config.ts @@ -3,7 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -// This file is strictly used for private development +// This file is for internal testing. // Nothing in this file should have a truthy value on mainline -export const betaUrl = '' +export const betaUrl = { + amazonq: '', + toolkit: '', +} diff --git a/packages/core/src/dev/index.ts b/packages/core/src/dev/index.ts index e69b1908875..111920c99ee 100644 --- a/packages/core/src/dev/index.ts +++ b/packages/core/src/dev/index.ts @@ -4,3 +4,4 @@ */ export { DevOptions, updateDevMode } from './activation' +export * as beta from './beta' diff --git a/packages/core/src/dynamicResources/awsResourceManager.ts b/packages/core/src/dynamicResources/awsResourceManager.ts index 255acfd915e..a6045559675 100644 --- a/packages/core/src/dynamicResources/awsResourceManager.ts +++ b/packages/core/src/dynamicResources/awsResourceManager.ts @@ -3,8 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { writeFileSync } from 'fs' -import { remove } from 'fs-extra' +import { writeFileSync } from 'fs' // eslint-disable-line no-restricted-imports import * as path from 'path' import * as vscode from 'vscode' import { CloudFormationClient } from '../shared/clients/cloudFormationClient' @@ -20,6 +19,7 @@ import { ResourceNode } from './explorer/nodes/resourceNode' import { ResourceTypeNode } from './explorer/nodes/resourceTypeNode' import { isCloud9 } from '../shared/extensionUtilities' import globals from '../shared/extensionGlobals' +import { fs } from '../shared' export const resourceFileGlobPattern = '**/*.awsResource.json' @@ -97,7 +97,7 @@ export class AwsResourceManager { } if (uri.scheme === 'file') { - await remove(uri.fsPath) + await fs.delete(uri.fsPath) globals.schemaService.registerMapping({ uri, diff --git a/packages/core/src/eventSchemas/commands/downloadSchemaItemCode.ts b/packages/core/src/eventSchemas/commands/downloadSchemaItemCode.ts index 5077b21c909..3ee5d8c865c 100644 --- a/packages/core/src/eventSchemas/commands/downloadSchemaItemCode.ts +++ b/packages/core/src/eventSchemas/commands/downloadSchemaItemCode.ts @@ -139,7 +139,7 @@ export class SchemaCodeDownloader { } catch (err) { const error = err as Error if (error.name === 'ResourceNotFound') { - //If the code generation wasn't previously kicked off, do so + // If the code generation wasn't previously kicked off, do so void vscode.window.showInformationMessage( localize( 'AWS.message.info.schemas.downloadCodeBindings.generate', @@ -149,10 +149,10 @@ export class SchemaCodeDownloader { ) await this.generator.generate(request) - //Then, poll for completion + // Then, poll for completion await this.poller.pollForCompletion(request) - //Download generated code bindings + // Download generated code bindings void vscode.window.showInformationMessage( localize( 'AWS.message.info.schemas.downloadCodeBindings.downloading', @@ -294,7 +294,7 @@ export class CodeExtractor { const codeZipFile = path.join(codeZipDir, fileName) const destinationDirectory = request.destinationDirectory.fsPath - //write binary data into a temp zip file in a temp directory + // write binary data into a temp zip file in a temp directory const zipContentsBinary = new Uint8Array(zipContents) const fd = fs.openSync(codeZipFile, 'w') fs.writeSync(fd, zipContentsBinary, 0, zipContentsBinary.byteLength, 0) diff --git a/packages/core/src/eventSchemas/commands/viewSchemaItem.ts b/packages/core/src/eventSchemas/commands/viewSchemaItem.ts index 5ad255e65c6..f3eca0428e5 100644 --- a/packages/core/src/eventSchemas/commands/viewSchemaItem.ts +++ b/packages/core/src/eventSchemas/commands/viewSchemaItem.ts @@ -51,5 +51,5 @@ export async function showSchemaContent( language: 'json', }) const editor = await vscode.window.showTextDocument(newDoc, vscode.ViewColumn.One, false) - await editor.edit((edit) => edit.insert(new vscode.Position(/*line*/ 0, /*character*/ 0), prettySchemaContent)) + await editor.edit((edit) => edit.insert(new vscode.Position(/* line*/ 0, /* character*/ 0), prettySchemaContent)) } diff --git a/packages/core/src/eventSchemas/models/schemaCodeLangs.ts b/packages/core/src/eventSchemas/models/schemaCodeLangs.ts index d182a8fb704..c2bec2e29f6 100644 --- a/packages/core/src/eventSchemas/models/schemaCodeLangs.ts +++ b/packages/core/src/eventSchemas/models/schemaCodeLangs.ts @@ -54,7 +54,16 @@ export function getLanguageDetails(language: SchemaCodeLangs): { } export function supportsEventBridgeTemplates(runtime: Runtime): boolean { - return ['python3.7', 'python3.8', 'python3.9', 'python3.10', 'python3.11', 'python3.12', 'go1.x'].includes(runtime) + return [ + 'python3.7', + 'python3.8', + 'python3.9', + 'python3.10', + 'python3.11', + 'python3.12', + 'python3.13', + 'go1.x', + ].includes(runtime) } export function getApiValueForSchemasDownload(runtime: Runtime): string { diff --git a/packages/core/src/eventSchemas/providers/schemasDataProvider.ts b/packages/core/src/eventSchemas/providers/schemasDataProvider.ts index 23c43c1915f..ec280238183 100644 --- a/packages/core/src/eventSchemas/providers/schemasDataProvider.ts +++ b/packages/core/src/eventSchemas/providers/schemasDataProvider.ts @@ -76,7 +76,7 @@ export class SchemasDataProvider { if (!schemas || schemas.length === 0) { schemas = await toArrayAsync(client.listSchemas(registryName)) const singleItem: registrySchemasMap = { registryName: registryName, schemaList: schemas } - //wizard setup always calls getRegistries method prior to getSchemas, so this shouldn't be undefined + // wizard setup always calls getRegistries method prior to getSchemas, so this shouldn't be undefined if (!registrySchemasMapList) { this.pushRegionDataIntoCache(region, [], [singleItem], credentials) } diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index 0359a724957..00fd730b490 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -16,10 +16,10 @@ import * as nls from 'vscode-nls' import globals, { initialize, isWeb } from './shared/extensionGlobals' import { join } from 'path' import { Commands } from './shared/vscode/commands2' -import { documentationUrl, endpointsFileUrl, githubCreateIssueUrl, githubUrl } from './shared/constants' -import { getIdeProperties, aboutExtension, isCloud9 } from './shared/extensionUtilities' +import { endpointsFileUrl, githubCreateIssueUrl, githubUrl } from './shared/constants' +import { getIdeProperties, aboutExtension, isCloud9, getDocUrl } from './shared/extensionUtilities' import { logAndShowError, logAndShowWebviewError } from './shared/utilities/logAndShowUtils' -import { AuthStatus, telemetry } from './shared/telemetry/telemetry' +import { telemetry } from './shared/telemetry/telemetry' import { openUrl } from './shared/utilities/vsCodeUtils' import { activateViewsShared } from './awsexplorer/activationShared' import fs from './shared/fs/fs' @@ -45,17 +45,12 @@ import { UriHandler } from './shared/vscode/uriHandler' import { disableAwsSdkWarning } from './shared/awsClientBuilder' import { FileResourceFetcher } from './shared/resourcefetcher/fileResourceFetcher' import { ResourceFetcher } from './shared/resourcefetcher/resourcefetcher' -import { ExtStartUpSources } from './shared/telemetry/util' -import { ExtensionUse, getAuthFormIdsFromConnection } from './auth/utils' -import { Auth } from './auth' -import { AuthFormId } from './login/webview/vue/types' -import { getTelemetryMetadataForConn, isSsoConnection } from './auth/connection' import { registerCommands } from './commands' // In web mode everything must be in a single file, so things like the endpoints file will not be available. // The following imports the endpoints file, which causes webpack to bundle it in the final output file import endpoints from '../resources/endpoints.json' -import { getLogger, setupUninstallHandler } from './shared' +import { getLogger, maybeShowMinVscodeWarning, setupUninstallHandler } from './shared' import { showViewLogsMessage } from './shared/utilities/messages' disableAwsSdkWarning() @@ -80,7 +75,7 @@ export async function activateCommon( errors.init(fs.getUsername(), isAutomation()) await initializeComputeRegion() - globals.contextPrefix = '' //todo: disconnect supplied argument + globals.contextPrefix = '' // todo: disconnect supplied argument registerCommandErrorHandler((info, error) => { const defaultMessage = localize('AWS.generic.message.error', 'Failed to run command: {0}', info.id) @@ -88,13 +83,13 @@ export async function activateCommon( }) registerWebviewErrorHandler((error: unknown, webviewId: string, command: string) => { - logAndShowWebviewError(localize, error, webviewId, command) + return logAndShowWebviewError(localize, error, webviewId, command) }) // Setup the logger const toolkitOutputChannel = vscode.window.createOutputChannel('AWS Toolkit', { log: true }) const toolkitLogChannel = vscode.window.createOutputChannel('AWS Toolkit Logs', { log: true }) - await activateLogger(context, contextPrefix, toolkitOutputChannel, toolkitLogChannel) + await activateLogger(context, contextPrefix, toolkitLogChannel, toolkitOutputChannel) globals.outputChannel = toolkitOutputChannel globals.logOutputChannel = toolkitLogChannel @@ -102,6 +97,8 @@ export async function activateCommon( getLogger().error('fs.init: invalid home directory given by env vars: %O', homeDirLogs) } + void maybeShowMinVscodeWarning('1.83.0') + if (isCloud9()) { vscode.window.withProgress = wrapWithProgressForCloud9(globals.outputChannel) context.subscriptions.push( @@ -115,7 +112,7 @@ export async function activateCommon( ) } - //setup globals + // setup globals globals.machineId = await getMachineId() globals.awsContext = new DefaultAwsContext() globals.sdkClientBuilder = new DefaultAWSClientBuilder(globals.awsContext) @@ -149,13 +146,13 @@ export async function activateCommon( ), // register URLs in extension menu Commands.register(`aws.toolkit.help`, async () => { - void openUrl(vscode.Uri.parse(documentationUrl)) + void openUrl(getDocUrl()) telemetry.aws_help.emit() }) ) // Handle AWS Toolkit un-installation. - setupUninstallHandler(VSCODE_EXTENSION_ID.awstoolkit, context) + setupUninstallHandler(VSCODE_EXTENSION_ID.awstoolkit, context.extension.id, context) // auth await initializeAuth(globals.loginManager) @@ -256,51 +253,3 @@ function wrapWithProgressForCloud9(channel: vscode.OutputChannel): (typeof vscod }) } } - -export async function emitUserState() { - await telemetry.auth_userState.run(async () => { - telemetry.record({ passive: true }) - - const firstUse = ExtensionUse.instance.isFirstUse() - const wasUpdated = ExtensionUse.instance.wasUpdated() - - if (firstUse) { - telemetry.record({ source: ExtStartUpSources.firstStartUp }) - } else if (wasUpdated) { - telemetry.record({ source: ExtStartUpSources.update }) - } else { - telemetry.record({ source: ExtStartUpSources.reload }) - } - - let authStatus: AuthStatus = 'notConnected' - const enabledConnections: Set = new Set() - const enabledScopes: Set = new Set() - if (Auth.instance.hasConnections) { - authStatus = 'expired' - ;(await Auth.instance.listConnections()).forEach((conn) => { - const state = Auth.instance.getConnectionState(conn) - if (state === 'valid') { - authStatus = 'connected' - } - - getAuthFormIdsFromConnection(conn).forEach((id) => enabledConnections.add(id)) - if (isSsoConnection(conn)) { - conn.scopes?.forEach((s) => enabledScopes.add(s)) - } - }) - } - - // There may be other SSO connections in toolkit, but there is no use case for - // displaying registration info for non-active connections at this time. - const activeConn = Auth.instance.activeConnection - if (activeConn?.type === 'sso') { - telemetry.record(await getTelemetryMetadataForConn(activeConn)) - } - - telemetry.record({ - authStatus, - authEnabledConnections: [...enabledConnections].join(','), - authScopes: [...enabledScopes].join(','), - }) - }) -} diff --git a/packages/core/src/extensionNode.ts b/packages/core/src/extensionNode.ts index aa81b943239..b6d4a599ce1 100644 --- a/packages/core/src/extensionNode.ts +++ b/packages/core/src/extensionNode.ts @@ -7,10 +7,9 @@ import * as vscode from 'vscode' import * as nls from 'vscode-nls' import * as codecatalyst from './codecatalyst/activation' +import { activate as activateAppBuilder } from './awsService/appBuilder/activation' import { activate as activateAwsExplorer } from './awsexplorer/activation' import { activate as activateCloudWatchLogs } from './awsService/cloudWatchLogs/activation' -import { CredentialsProviderManager } from './auth/providers/credentialsProviderManager' -import { SharedCredentialsProviderFactory } from './auth/providers/sharedCredentialsProviderFactory' import { activate as activateSchemas } from './eventSchemas/activation' import { activate as activateLambda } from './lambda/activation' import { activate as activateCloudFormationTemplateRegistry } from './shared/cloudformation/activation' @@ -24,7 +23,7 @@ import { } from './shared/extensionUtilities' import { getLogger, Logger } from './shared/logger/logger' import { activate as activateEcr } from './awsService/ecr/activation' -import { activate as activateEc2 } from './awsService/ec2/activation' +import { activate as activateEc2, deactivate as deactivateEc2 } from './awsService/ec2/activation' import { activate as activateSam } from './shared/sam/activation' import { activate as activateS3 } from './awsService/s3/activation' import * as filetypes from './shared/filetypes' @@ -36,30 +35,31 @@ import { activate as activateEcs } from './awsService/ecs/activation' import { activate as activateAppRunner } from './awsService/apprunner/activation' import { activate as activateIot } from './awsService/iot/activation' import { activate as activateDev } from './dev/activation' +import * as beta from './dev/beta' import { activate as activateApplicationComposer } from './applicationcomposer/activation' import { activate as activateRedshift } from './awsService/redshift/activation' import { activate as activateIamPolicyChecks } from './awsService/accessanalyzer/activation' -import { Ec2CredentialsProvider } from './auth/providers/ec2CredentialsProvider' -import { EnvVarsCredentialsProvider } from './auth/providers/envVarsCredentialsProvider' -import { EcsCredentialsProvider } from './auth/providers/ecsCredentialsProvider' +import { activate as activateNotifications } from './notifications/activation' import { SchemaService } from './shared/schemas' import { AwsResourceManager } from './dynamicResources/awsResourceManager' import globals from './shared/extensionGlobals' import { Experiments, Settings, showSettingsFailedMsg } from './shared/settings' import { isReleaseVersion } from './shared/vscode/env' -import { telemetry } from './shared/telemetry/telemetry' +import { AuthStatus, AuthUserState, telemetry } from './shared/telemetry/telemetry' import { Auth, SessionSeparationPrompt } from './auth/auth' +import { getTelemetryMetadataForConn } from './auth/connection' import { registerSubmitFeedback } from './feedback/vue/submitFeedback' -import { activateCommon, deactivateCommon, emitUserState } from './extension' +import { activateCommon, deactivateCommon } from './extension' import { learnMoreAmazonQCommand, qExtensionPageCommand, dismissQTree } from './amazonq/explorer/amazonQChildrenNodes' import { AuthUtil, codeWhispererCoreScopes, isPreviousQUser } from './codewhisperer/util/authUtil' import { installAmazonQExtension } from './codewhisperer/commands/basicCommands' import { isExtensionInstalled, VSCODE_EXTENSION_ID } from './shared/utilities' -import { ExtensionUse } from './auth/utils' +import { ExtensionUse, getAuthFormIdsFromConnection, initializeCredentialsProviderManager } from './auth/utils' import { ExtStartUpSources } from './shared/telemetry' import { activate as activateThreatComposerEditor } from './threatComposer/activation' import { isSsoConnection, hasScopes } from './auth/connection' -import { setContext } from './shared' +import { CrashMonitoring, setContext } from './shared' +import { AuthFormId } from './login/webview/vue/types' let localize: nls.LocalizeFunc @@ -78,6 +78,9 @@ export async function activate(context: vscode.ExtensionContext) { // IMPORTANT: If you are doing setup that should also work in web mode (browser), it should be done in the function below const extContext = await activateCommon(context, contextPrefix, false) + // Intentionally do not await since this can be slow and non-critical + void (await CrashMonitoring.instance())?.start() + initializeCredentialsProviderManager() const toolkitEnvDetails = getExtEnvironmentDetails() @@ -107,6 +110,7 @@ export async function activate(context: vscode.ExtensionContext) { try { await activateDev(context) + await beta.activate(context) } catch (error) { getLogger().debug(`Developer Tools (internal): failed to activate: ${(error as Error).message}`) } @@ -205,6 +209,8 @@ export async function activate(context: vscode.ExtensionContext) { await activateRedshift(extContext) + await activateAppBuilder(extContext) + await activateIamPolicyChecks(extContext) context.subscriptions.push( @@ -230,7 +236,16 @@ export async function activate(context: vscode.ExtensionContext) { globals.telemetry.assertPassiveTelemetry(globals.didReload) } - await emitUserState() + // TODO: Should probably emit for web as well. + // Will the web metric look the same? + telemetry.auth_userState.emit({ + passive: true, + result: 'Succeeded', + source: ExtensionUse.instance.sourceForTelemetry(), + ...(await getAuthState()), + }) + + void activateNotifications(context, getAuthState) } catch (error) { const stacktrace = (error as Error).stack?.split('\n') // truncate if the stacktrace is unusually long @@ -251,7 +266,8 @@ export async function activate(context: vscode.ExtensionContext) { } export async function deactivate() { - await deactivateCommon() + // Run concurrently to speed up execution. stop() does not throw so it is safe + await Promise.all([await (await CrashMonitoring.instance())?.shutdown(), deactivateCommon(), deactivateEc2()]) await globals.resourceManager.dispose() } @@ -306,12 +322,6 @@ async function handleAmazonQInstall() { }) } -function initializeCredentialsProviderManager() { - const manager = CredentialsProviderManager.getInstance() - manager.addProviderFactory(new SharedCredentialsProviderFactory()) - manager.addProviders(new Ec2CredentialsProvider(), new EcsCredentialsProvider(), new EnvVarsCredentialsProvider()) -} - function recordToolkitInitialization(activationStartedOn: number, settingsValid: boolean, logger?: Logger) { try { const activationFinishedOn = Date.now() @@ -326,3 +336,36 @@ function recordToolkitInitialization(activationStartedOn: number, settingsValid: logger?.error(err as Error) } } + +async function getAuthState(): Promise> { + let authStatus: AuthStatus = 'notConnected' + const enabledConnections: Set = new Set() + const enabledScopes: Set = new Set() + if (Auth.instance.hasConnections) { + authStatus = 'expired' + ;(await Auth.instance.listConnections()).forEach((conn) => { + const state = Auth.instance.getConnectionState(conn) + if (state === 'valid') { + authStatus = 'connected' + } + + getAuthFormIdsFromConnection(conn).forEach((id) => enabledConnections.add(id)) + if (isSsoConnection(conn)) { + conn.scopes?.forEach((s) => enabledScopes.add(s)) + } + }) + } + + // There may be other SSO connections in toolkit, but there is no use case for + // displaying registration info for non-active connections at this time. + const activeConn = Auth.instance.activeConnection + if (activeConn?.type === 'sso') { + telemetry.record(await getTelemetryMetadataForConn(activeConn)) + } + + return { + authStatus, + authEnabledConnections: [...enabledConnections].sort().join(','), + authScopes: [...enabledScopes].sort().join(','), + } +} diff --git a/packages/core/src/feedback/vue/submitFeedback.ts b/packages/core/src/feedback/vue/submitFeedback.ts index 0125059550c..838030a4b88 100644 --- a/packages/core/src/feedback/vue/submitFeedback.ts +++ b/packages/core/src/feedback/vue/submitFeedback.ts @@ -71,7 +71,7 @@ export class FeedbackWebview extends VueWebview { } } -type FeedbackId = 'AWS Toolkit' | 'Amazon Q' | 'Application Composer' | 'Threat Composer' +type FeedbackId = 'AWS Toolkit' | 'Amazon Q' | 'Infrastructure Composer' | 'Threat Composer' let _submitFeedback: | RegisteredCommand<(_: VsCodeCommandArg, id: FeedbackId, commentData?: string) => Promise> diff --git a/packages/core/src/lambda/activation.ts b/packages/core/src/lambda/activation.ts index 911f3f9be44..4a21b2e9611 100644 --- a/packages/core/src/lambda/activation.ts +++ b/packages/core/src/lambda/activation.ts @@ -11,25 +11,36 @@ import { downloadLambdaCommand } from './commands/downloadLambda' import { tryRemoveFolder } from '../shared/filesystemUtilities' import { ExtContext } from '../shared/extensions' import { invokeRemoteLambda } from './vue/remoteInvoke/invokeLambda' -import { registerSamInvokeVueCommand } from './vue/configEditor/samInvokeBackend' +import { registerSamDebugInvokeVueCommand, registerSamInvokeVueCommand } from './vue/configEditor/samInvokeBackend' import { Commands } from '../shared/vscode/commands2' import { DefaultLambdaClient } from '../shared/clients/lambdaClient' import { copyLambdaUrl } from './commands/copyLambdaUrl' +import { ResourceNode } from '../awsService/appBuilder/explorer/nodes/resourceNode' +import { isTreeNode, TreeNode } from '../shared/treeview/resourceTreeDataProvider' +import { getSourceNode } from '../shared/utilities/treeNodeUtils' /** * Activates Lambda components. */ export async function activate(context: ExtContext): Promise { context.extensionContext.subscriptions.push( - Commands.register('aws.deleteLambda', async (node: LambdaFunctionNode) => { - await deleteLambda(node.configuration, new DefaultLambdaClient(node.regionCode)) - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node.parent) + Commands.register('aws.deleteLambda', async (node: LambdaFunctionNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await deleteLambda(sourceNode.configuration, new DefaultLambdaClient(sourceNode.regionCode)) + await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', sourceNode.parent) + }), + Commands.register('aws.invokeLambda', async (node: LambdaFunctionNode | TreeNode) => { + let source: string = 'AwsExplorerRemoteInvoke' + if (isTreeNode(node)) { + node = getSourceNode(node) + source = 'AppBuilderRemoteInvoke' + } + await invokeRemoteLambda(context, { + outputChannel: context.outputChannel, + functionNode: node, + source: source, + }) }), - Commands.register( - 'aws.invokeLambda', - async (node: LambdaFunctionNode) => - await invokeRemoteLambda(context, { outputChannel: context.outputChannel, functionNode: node }) - ), // Capture debug finished events, and delete the temporary directory if it exists vscode.debug.onDidTerminateDebugSession(async (session) => { if ( @@ -39,7 +50,10 @@ export async function activate(context: ExtContext): Promise { await tryRemoveFolder(session.configuration.baseBuildDir) } }), - Commands.register('aws.downloadLambda', async (node: LambdaFunctionNode) => await downloadLambdaCommand(node)), + Commands.register('aws.downloadLambda', async (node: LambdaFunctionNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await downloadLambdaCommand(sourceNode) + }), Commands.register({ id: 'aws.uploadLambda', autoconnect: true }, async (arg?: unknown) => { if (arg instanceof LambdaFunctionNode) { await uploadLambdaCommand({ @@ -53,10 +67,15 @@ export async function activate(context: ExtContext): Promise { await uploadLambdaCommand() } }), - Commands.register( - 'aws.copyLambdaUrl', - async (node: LambdaFunctionNode) => await copyLambdaUrl(node, new DefaultLambdaClient(node.regionCode)) - ), - registerSamInvokeVueCommand(context) + Commands.register('aws.copyLambdaUrl', async (node: LambdaFunctionNode | TreeNode) => { + const sourceNode = getSourceNode(node) + await copyLambdaUrl(sourceNode, new DefaultLambdaClient(sourceNode.regionCode)) + }), + + registerSamInvokeVueCommand(context), + + Commands.register('aws.launchDebugConfigForm', async (node: ResourceNode) => + registerSamDebugInvokeVueCommand(context, { resource: node }) + ) ) } diff --git a/packages/core/src/lambda/commands/createNewSamApp.ts b/packages/core/src/lambda/commands/createNewSamApp.ts index d789eefd688..629130dbb2a 100644 --- a/packages/core/src/lambda/commands/createNewSamApp.ts +++ b/packages/core/src/lambda/commands/createNewSamApp.ts @@ -39,16 +39,20 @@ import { isTemplateTargetProperties } from '../../shared/sam/debugger/awsSamDebu import { TemplateTargetProperties } from '../../shared/sam/debugger/awsSamDebugConfiguration' import { openLaunchJsonFile } from '../../shared/sam/debugger/commands/addSamDebugConfiguration' import { waitUntil } from '../../shared/utilities/timeoutUtils' -import { debugNewSamAppUrl, launchConfigDocUrl } from '../../shared/constants' -import { getIdeProperties, isCloud9 } from '../../shared/extensionUtilities' -import { execFileSync } from 'child_process' -import { writeFile } from 'fs-extra' +import { + getIdeProperties, + getDebugNewSamAppDocUrl, + isCloud9, + getLaunchConfigDocUrl, +} from '../../shared/extensionUtilities' import { checklogs } from '../../shared/localizedText' import globals from '../../shared/extensionGlobals' import { telemetry } from '../../shared/telemetry/telemetry' import { LambdaArchitecture, Result, Runtime } from '../../shared/telemetry/telemetry' import { getTelemetryReason, getTelemetryResult } from '../../shared/errors' import { openUrl, replaceVscodeVars } from '../../shared/utilities/vsCodeUtils' +import { fs } from '../../shared' +import { ChildProcess } from '../../shared/utilities/processUtils' export const samInitTemplateFiles: string[] = ['template.yaml', 'template.yml'] export const samInitReadmeFile: string = 'README.TOOLKIT.md' @@ -214,7 +218,9 @@ export async function createNewSamApplication( // Needs to be done or else gopls won't start if (goRuntimes.includes(createRuntime)) { try { - execFileSync('go', ['mod', 'tidy'], { cwd: path.join(path.dirname(templateUri.fsPath), 'hello-world') }) + await ChildProcess.run('go', ['mod', 'tidy'], { + spawnOptions: { cwd: path.join(path.dirname(templateUri.fsPath), 'hello-world') }, + }) } catch (err) { getLogger().warn( localize( @@ -316,7 +322,7 @@ export async function createNewSamApplication( ) .then(async (buttonText) => { if (buttonText === helpText) { - void openUrl(vscode.Uri.parse(launchConfigDocUrl)) + void openUrl(getLaunchConfigDocUrl()) } }) } @@ -441,7 +447,7 @@ async function showCompletionNotification(appName: string, configs: string): Pro if (action === openJson) { await openLaunchJsonFile() } else if (action === learnMore) { - void openUrl(vscode.Uri.parse(debugNewSamAppUrl)) + void openUrl(getDebugNewSamAppDocUrl()) } } @@ -472,7 +478,7 @@ export async function writeToolkitReadme( : 'https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/serverless-apps.html' ) - await writeFile(readmeLocation, readme) + await fs.writeFile(readmeLocation, readme) getLogger().debug(`writeToolkitReadme: wrote file: %O`, readmeLocation) return true diff --git a/packages/core/src/lambda/commands/deploySamApplication.ts b/packages/core/src/lambda/commands/deploySamApplication.ts deleted file mode 100644 index 4231b2b614e..00000000000 --- a/packages/core/src/lambda/commands/deploySamApplication.ts +++ /dev/null @@ -1,326 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' - -import { asEnvironmentVariables } from '../../auth/credentials/utils' -import { AwsContext } from '../../shared/awsContext' -import globals from '../../shared/extensionGlobals' - -import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities' -import { checklogs } from '../../shared/localizedText' -import { getLogger } from '../../shared/logger' -import { SamCliBuildInvocation } from '../../shared/sam/cli/samCliBuild' -import { SamCliSettings } from '../../shared/sam/cli/samCliSettings' -import { getSamCliContext, SamCliContext, getSamCliVersion } from '../../shared/sam/cli/samCliContext' -import { runSamCliDeploy } from '../../shared/sam/cli/samCliDeploy' -import { SamCliProcessInvoker } from '../../shared/sam/cli/samCliInvokerUtils' -import { runSamCliPackage } from '../../shared/sam/cli/samCliPackage' -import { throwAndNotifyIfInvalid } from '../../shared/sam/cli/samCliValidationUtils' -import { Result } from '../../shared/telemetry/telemetry' -import { addCodiconToString } from '../../shared/utilities/textUtilities' -import { SamDeployWizardResponse } from '../wizards/samDeployWizard' -import { telemetry } from '../../shared/telemetry/telemetry' - -const localize = nls.loadMessageBundle() - -interface DeploySamApplicationParameters { - sourceTemplatePath: string - deployRootFolder: string - environmentVariables: NodeJS.ProcessEnv - region: string - packageBucketName: string - ecrRepo?: string - destinationStackName: string - parameterOverrides: Map -} - -export interface WindowFunctions { - showInformationMessage: typeof vscode.window.showInformationMessage - showErrorMessage: typeof vscode.window.showErrorMessage - setStatusBarMessage(text: string, hideWhenDone: Thenable): vscode.Disposable -} - -export async function deploySamApplication( - { - samCliContext = getSamCliContext(), - samDeployWizard, - }: { - samCliContext?: SamCliContext - samDeployWizard: () => Promise - }, - { - awsContext, - settings, - window = getDefaultWindowFunctions(), - refreshFn = () => { - // no need to await, doesn't need to block further execution (true -> no telemetry) - void vscode.commands.executeCommand('aws.refreshAwsExplorer', true) - }, - }: { - awsContext: Pick - settings: SamCliSettings - window?: WindowFunctions - refreshFn?: () => void - } -): Promise { - let deployResult: Result = 'Succeeded' - let samVersion: string | undefined - let deployFolder: string | undefined - try { - const credentials = await awsContext.getCredentials() - if (!credentials) { - throw new Error('No AWS profile selected') - } - - throwAndNotifyIfInvalid(await samCliContext.validator.detectValidSamCli()) - - const deployWizardResponse = await samDeployWizard() - - if (!deployWizardResponse) { - return - } - - deployFolder = await makeTemporaryToolkitFolder('samDeploy') - samVersion = await getSamCliVersion(samCliContext) - - const deployParameters: DeploySamApplicationParameters = { - deployRootFolder: deployFolder, - destinationStackName: deployWizardResponse.stackName, - packageBucketName: deployWizardResponse.s3Bucket, - ecrRepo: deployWizardResponse.ecrRepo?.repositoryUri, - parameterOverrides: deployWizardResponse.parameterOverrides, - environmentVariables: asEnvironmentVariables(credentials), - region: deployWizardResponse.region, - sourceTemplatePath: deployWizardResponse.template.fsPath, - } - - const deployApplicationPromise = deploy({ - deployParameters, - invoker: samCliContext.invoker, - window, - }) - - window.setStatusBarMessage( - addCodiconToString( - 'cloud-upload', - localize( - 'AWS.samcli.deploy.statusbar.message', - 'Deploying SAM Application to {0}...', - deployWizardResponse.stackName - ) - ), - deployApplicationPromise - ) - - await deployApplicationPromise - refreshFn() - - // successful deploy: retain S3 bucket for quick future access - const profile = awsContext.getCredentialProfileName() - if (profile) { - await settings.updateSavedBuckets(profile, deployWizardResponse.region, deployWizardResponse.s3Bucket) - } else { - getLogger().warn('Profile not provided; cannot write recent buckets.') - } - } catch (err) { - deployResult = 'Failed' - outputDeployError(err as Error) - void vscode.window.showErrorMessage( - localize('AWS.samcli.deploy.workflow.error', 'Failed to deploy SAM application.') - ) - } finally { - await tryRemoveFolder(deployFolder) - telemetry.sam_deploy.emit({ result: deployResult, version: samVersion }) - } -} - -function getBuildRootFolder(deployRootFolder: string): string { - return path.join(deployRootFolder, 'build') -} - -function getBuildTemplatePath(deployRootFolder: string): string { - // Assumption: sam build will always produce a template.yaml file. - // If that is not the case, revisit this logic. - return path.join(getBuildRootFolder(deployRootFolder), 'template.yaml') -} - -function getPackageTemplatePath(deployRootFolder: string): string { - return path.join(deployRootFolder, 'template.yaml') -} - -async function buildOperation(params: { - deployParameters: DeploySamApplicationParameters - invoker: SamCliProcessInvoker -}): Promise { - try { - getLogger().info(localize('AWS.samcli.deploy.workflow.init', 'Building SAM Application...')) - - const buildDestination = getBuildRootFolder(params.deployParameters.deployRootFolder) - - const build = new SamCliBuildInvocation({ - buildDir: buildDestination, - baseDir: undefined, - templatePath: params.deployParameters.sourceTemplatePath, - invoker: params.invoker, - }) - - await build.execute() - - return true - } catch (err) { - getLogger().warn( - localize( - 'AWS.samcli.build.failedBuild', - '"sam build" failed: {0}', - params.deployParameters.sourceTemplatePath - ) - ) - return false - } -} - -async function packageOperation( - params: { - deployParameters: DeploySamApplicationParameters - invoker: SamCliProcessInvoker - }, - buildSuccessful: boolean -): Promise { - if (!buildSuccessful) { - void vscode.window.showInformationMessage( - localize( - 'AWS.samcli.deploy.workflow.packaging.noBuild', - 'Attempting to package source template directory directly since "sam build" failed' - ) - ) - } - - getLogger().info( - localize( - 'AWS.samcli.deploy.workflow.packaging', - 'Packaging SAM Application to S3 Bucket: {0}', - params.deployParameters.packageBucketName - ) - ) - - // HACK: Attempt to package the initial template if the build fails. - const buildTemplatePath = buildSuccessful - ? getBuildTemplatePath(params.deployParameters.deployRootFolder) - : params.deployParameters.sourceTemplatePath - const packageTemplatePath = getPackageTemplatePath(params.deployParameters.deployRootFolder) - - await runSamCliPackage( - { - sourceTemplateFile: buildTemplatePath, - destinationTemplateFile: packageTemplatePath, - environmentVariables: params.deployParameters.environmentVariables, - region: params.deployParameters.region, - s3Bucket: params.deployParameters.packageBucketName, - ecrRepo: params.deployParameters.ecrRepo, - }, - params.invoker - ) -} - -async function deployOperation(params: { - deployParameters: DeploySamApplicationParameters - invoker: SamCliProcessInvoker -}): Promise { - try { - getLogger().info( - localize( - 'AWS.samcli.deploy.workflow.stackName.initiated', - 'Deploying SAM Application to CloudFormation Stack: {0}', - params.deployParameters.destinationStackName - ) - ) - - const packageTemplatePath = getPackageTemplatePath(params.deployParameters.deployRootFolder) - - await runSamCliDeploy( - { - parameterOverrides: params.deployParameters.parameterOverrides, - environmentVariables: params.deployParameters.environmentVariables, - templateFile: packageTemplatePath, - region: params.deployParameters.region, - stackName: params.deployParameters.destinationStackName, - s3Bucket: params.deployParameters.packageBucketName, - ecrRepo: params.deployParameters.ecrRepo, - }, - params.invoker - ) - } catch (err) { - // Handle sam deploy Errors to supplement the error message prior to writing it out - const error = err as Error - - getLogger().error(error) - - const errorMessage = enhanceAwsCloudFormationInstructions(String(err), params.deployParameters) - globals.outputChannel.appendLine(errorMessage) - - throw new Error('Deploy failed') - } -} - -async function deploy(params: { - deployParameters: DeploySamApplicationParameters - invoker: SamCliProcessInvoker - window: WindowFunctions -}): Promise { - globals.outputChannel.show(true) - getLogger().info(localize('AWS.samcli.deploy.workflow.start', 'Starting SAM Application deployment...')) - - const buildSuccessful = await buildOperation(params) - await packageOperation(params, buildSuccessful) - await deployOperation(params) - - getLogger().info( - localize( - 'AWS.samcli.deploy.workflow.success', - 'Deployed SAM Application to CloudFormation Stack: {0}', - params.deployParameters.destinationStackName - ) - ) - - void params.window.showInformationMessage( - localize('AWS.samcli.deploy.workflow.success.general', 'SAM Application deployment succeeded.') - ) -} - -function enhanceAwsCloudFormationInstructions( - message: string, - deployParameters: DeploySamApplicationParameters -): string { - // detect error message from https://github.com/aws/aws-cli/blob/4ff0cbacbac69a21d4dd701921fe0759cf7852ed/awscli/customizations/cloudformation/exceptions.py#L42 - // and append region to assist in troubleshooting the error - // (command uses CLI configured value--users that don't know this and omit region won't see error) - if ( - message.includes( - `aws cloudformation describe-stack-events --stack-name ${deployParameters.destinationStackName}` - ) - ) { - message += ` --region ${deployParameters.region}` - } - - return message -} - -function outputDeployError(error: Error) { - getLogger().error(error) - - globals.outputChannel.show(true) - getLogger().error('AWS.samcli.deploy.general.error', 'Error deploying a SAM Application. {0}', checklogs()) -} - -function getDefaultWindowFunctions(): WindowFunctions { - return { - setStatusBarMessage: vscode.window.setStatusBarMessage, - showErrorMessage: vscode.window.showErrorMessage, - showInformationMessage: vscode.window.showInformationMessage, - } -} diff --git a/packages/core/src/lambda/commands/downloadLambda.ts b/packages/core/src/lambda/commands/downloadLambda.ts index 64323df9036..16128ce5701 100644 --- a/packages/core/src/lambda/commands/downloadLambda.ts +++ b/packages/core/src/lambda/commands/downloadLambda.ts @@ -4,7 +4,6 @@ */ import AdmZip from 'adm-zip' -import * as fs from 'fs-extra' import * as _ from 'lodash' import * as path from 'path' import * as vscode from 'vscode' @@ -26,6 +25,7 @@ import { Progress } from 'got/dist/source' import { DefaultLambdaClient } from '../../shared/clients/lambdaClient' import { telemetry } from '../../shared/telemetry/telemetry' import { Result, Runtime } from '../../shared/telemetry/telemetry' +import { fs } from '../../shared' export async function downloadLambdaCommand(functionNode: LambdaFunctionNode) { const result = await runDownloadLambda(functionNode) @@ -57,7 +57,7 @@ async function runDownloadLambda(functionNode: LambdaFunctionNode): Promise parseSamListResourceOutput(output) + ) + // Filter out resources that are not deployed + return samCliListResourceOutput.filter((resource) => resource.PhysicalResourceId !== '-') + } catch (err) { + const error = err as Error + getLogger().error(error) + } +} + +function parseSamListResourceOutput(output: any): StackResource[] { + try { + if ((Array.isArray(output) && output.length === 0) || '[]' === output) { + // Handle if the output is instance or stringify version of an empty array to avoid parsing error + return [] + } + return JSON.parse(output) as StackResource[] + } catch (error: any) { + void vscode.window.showErrorMessage(`Failed to parse SAM CLI output: ${error.message}`) + return [] + } +} diff --git a/packages/core/src/lambda/commands/uploadLambda.ts b/packages/core/src/lambda/commands/uploadLambda.ts index f6af25d983b..7627d53cfab 100644 --- a/packages/core/src/lambda/commands/uploadLambda.ts +++ b/packages/core/src/lambda/commands/uploadLambda.ts @@ -12,7 +12,7 @@ import AdmZip from 'adm-zip' import * as path from 'path' import { fs } from '../../shared' import { showConfirmationMessage, showViewLogsMessage } from '../../shared/utilities/messages' -import { cloud9Findfile, makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities' +import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities' import * as localizedText from '../../shared/localizedText' import { getLogger } from '../../shared/logger' import { SamCliBuildInvocation } from '../../shared/sam/cli/samCliBuild' @@ -419,7 +419,7 @@ async function runUploadLambdaZipFile(lambda: LambdaFunction, zipFileUri: vscode cancellable: false, }, async (progress) => { - const zipFile = await fs.readFile(zipFileUri.fsPath).catch((err) => { + const zipFile = await fs.readFileBytes(zipFileUri.fsPath).catch((err) => { throw new ToolkitError('Failed to read zip', { cause: err }) }) return await uploadZipBuffer(lambda, zipFile, progress) @@ -494,17 +494,15 @@ export async function findApplicationJsonFile( } const isdir = await fs.existsDir(startPath.fsPath) const parentDir = isdir ? startPath.fsPath : path.dirname(startPath.fsPath) - const found = cloud9 - ? await cloud9Findfile(parentDir, '.application.json') - : await vscode.workspace.findFiles( - new vscode.RelativePattern(parentDir, '**/.application.json'), - // exclude: - // - null = NO excludes apply - // - undefined = default excludes apply (e.g. the `files.exclude` setting but not `search.exclude`). - // eslint-disable-next-line unicorn/no-null - null, - 1 - ) + const found = await vscode.workspace.findFiles( + new vscode.RelativePattern(parentDir, '**/.application.json'), + // exclude: + // - null = NO excludes apply + // - undefined = default excludes apply (e.g. the `files.exclude` setting but not `search.exclude`). + // eslint-disable-next-line unicorn/no-null + null, + 1 + ) if (!found || found.length === 0) { getLogger().debug('uploadLambda: .application.json not found in: "%s"', parentDir) } @@ -514,7 +512,7 @@ export async function findApplicationJsonFile( export async function getFunctionNames(file: vscode.Uri, region: string): Promise { try { const names: string[] = [] - const appData = JSON.parse(await fs.readFileAsString(file.fsPath)) + const appData = JSON.parse(await fs.readFileText(file.fsPath)) if (appData['Functions']) { const functions = Object.keys(appData['Functions']) if (functions) { diff --git a/packages/core/src/lambda/config/templates.ts b/packages/core/src/lambda/config/templates.ts index 6b0704e366e..5077551bacf 100644 --- a/packages/core/src/lambda/config/templates.ts +++ b/packages/core/src/lambda/config/templates.ts @@ -5,7 +5,6 @@ // Use jsonc-parser.parse instead of JSON.parse, as JSONC can handle comments. VS Code uses jsonc-parser // under the hood to provide symbols for JSON documents, so this will keep us consistent with VS code. -import { access, writeFile, ensureDir } from 'fs-extra' import * as jsonParser from 'jsonc-parser' import * as os from 'os' import * as _path from 'path' @@ -17,6 +16,8 @@ import { ReadonlyJsonObject } from '../../shared/sam/debugger/awsSamDebugConfigu import { getTabSizeSetting } from '../../shared/utilities/editorUtilities' import { getNormalizedRelativePath } from '../../shared/utilities/pathUtils' import { saveDocumentIfDirty } from '../../shared/utilities/textDocumentUtilities' +import { access } from 'fs/promises' +import { fs } from '../../shared' const localize = nls.loadMessageBundle() @@ -197,11 +198,11 @@ export function showTemplatesConfigurationError( } export async function ensureTemplatesConfigFileExists(path: string): Promise { - await ensureDir(_path.dirname(path)) + await fs.mkdir(_path.dirname(path)) try { await access(path) } catch { - await writeFile(path, '{}') + await fs.writeFile(path, '{}') } } diff --git a/packages/core/src/lambda/models/samLambdaRuntime.ts b/packages/core/src/lambda/models/samLambdaRuntime.ts index 42a8681c672..58311474d41 100644 --- a/packages/core/src/lambda/models/samLambdaRuntime.ts +++ b/packages/core/src/lambda/models/samLambdaRuntime.ts @@ -22,6 +22,7 @@ export enum RuntimeFamily { DotNet, Go, Java, + Ruby, } export type RuntimePackageType = 'Image' | 'Zip' @@ -29,6 +30,7 @@ export type RuntimePackageType = 'Image' | 'Zip' // TODO: Consolidate all of the runtime constructs into a single > map // We should be able to eliminate a fair amount of redundancy with that. export const nodeJsRuntimes: ImmutableSet = ImmutableSet([ + 'nodejs22.x', 'nodejs20.x', 'nodejs18.x', 'nodejs16.x', @@ -49,6 +51,7 @@ export function getNodeMajorVersion(version?: string): number | undefined { } export const pythonRuntimes: ImmutableSet = ImmutableSet([ + 'python3.13', 'python3.12', 'python3.11', 'python3.10', @@ -57,8 +60,15 @@ export const pythonRuntimes: ImmutableSet = ImmutableSet([ 'python3.7', ]) export const goRuntimes: ImmutableSet = ImmutableSet(['go1.x']) -export const javaRuntimes: ImmutableSet = ImmutableSet(['java17', 'java11', 'java8', 'java8.al2']) -export const dotNetRuntimes: ImmutableSet = ImmutableSet(['dotnet6']) +export const javaRuntimes: ImmutableSet = ImmutableSet([ + 'java17', + 'java11', + 'java8', + 'java8.al2', + 'java21', +]) +export const dotNetRuntimes: ImmutableSet = ImmutableSet(['dotnet6', 'dotnet8']) +export const rubyRuntimes: ImmutableSet = ImmutableSet(['ruby3.2', 'ruby3.3']) /** * Deprecated runtimes can be found at https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html @@ -77,13 +87,16 @@ export const deprecatedRuntimes: ImmutableSet = ImmutableSet([ 'nodejs8.10', 'nodejs10.x', 'nodejs12.x', + 'ruby2.5', + 'ruby2.7', ]) const defaultRuntimes = ImmutableMap([ - [RuntimeFamily.NodeJS, 'nodejs20.x'], - [RuntimeFamily.Python, 'python3.12'], - [RuntimeFamily.DotNet, 'dotnet6'], + [RuntimeFamily.NodeJS, 'nodejs22.x'], + [RuntimeFamily.Python, 'python3.13'], + [RuntimeFamily.DotNet, 'dotnet8'], [RuntimeFamily.Go, 'go1.x'], - [RuntimeFamily.Java, 'java17'], + [RuntimeFamily.Java, 'java21'], + [RuntimeFamily.Ruby, 'ruby3.3'], ]) export const samZipLambdaRuntimes: ImmutableSet = ImmutableSet.union([ @@ -97,6 +110,7 @@ export const samZipLambdaRuntimes: ImmutableSet = ImmutableSet.union([ export const samArmLambdaRuntimes: ImmutableSet = ImmutableSet([ 'python3.9', 'python3.8', + 'nodejs22.x', 'nodejs20.x', 'nodejs18.x', 'nodejs16.x', @@ -157,6 +171,8 @@ export function getFamily(runtime: string): RuntimeFamily { return RuntimeFamily.Go } else if (javaRuntimes.has(runtime)) { return RuntimeFamily.Java + } else if (rubyRuntimes.has(runtime)) { + return RuntimeFamily.Ruby } return RuntimeFamily.Unknown } @@ -206,6 +222,10 @@ export function getRuntimeFamily(langId: string): RuntimeFamily { return RuntimeFamily.Python case 'go': return RuntimeFamily.Go + case 'java': + return RuntimeFamily.Java + case 'ruby': + return RuntimeFamily.Ruby default: return RuntimeFamily.Unknown } @@ -258,7 +278,7 @@ export function createRuntimeQuickPick(params: { totalSteps?: number }): QuickPickPrompter { const zipRuntimes = params.runtimeFamily - ? getRuntimesForFamily(params.runtimeFamily) ?? samLambdaCreatableRuntimes() + ? (getRuntimesForFamily(params.runtimeFamily) ?? samLambdaCreatableRuntimes()) : samLambdaCreatableRuntimes() const zipRuntimeItems = zipRuntimes diff --git a/packages/core/src/lambda/vue/configEditor/samInvoke.css b/packages/core/src/lambda/vue/configEditor/samInvoke.css index d248e071a90..9ca2c8ef452 100644 --- a/packages/core/src/lambda/vue/configEditor/samInvoke.css +++ b/packages/core/src/lambda/vue/configEditor/samInvoke.css @@ -1,7 +1,3 @@ -form { - padding: 15px; -} - .section-header { margin: 0px; margin-bottom: 10px; @@ -10,7 +6,9 @@ form { } textarea { - max-width: 100%; + color: var(--vscode-settings-textInputForeground); + background: var(--vscode-settings-textInputBackground); + border: 1px solid var(--vscode-settings-textInputBorder); } .config-item { @@ -47,7 +45,133 @@ textarea { margin-bottom: 16px; } +.header-buttons { + display: flex; + align-items: center; + margin-bottom: 20px; +} + #target-type-selector { margin-bottom: 15px; margin-left: 8px; } + +.form-row { + display: grid; + grid-template-columns: 150px 1fr; + margin-bottom: 10px; +} + +.form-control { + min-width: 170%; /* Set a minimum width */ + width: 100%; /* Allow the width to adjust based on content */ + display: inline-block; + flex-grow: 1; + margin-right: 0.5rem; +} + +.payload-options-button { + display: grid; + align-items: center; + border: none; + padding: 5px 10px; + cursor: pointer; + font-size: 0.9em; + margin-bottom: 10px; +} + +.payload-options-buttons { + display: flex; + align-items: center; + margin-top: 10px; + margin-bottom: 10px; +} + +.Icontainer { + margin-inline: auto; + margin-top: 5rem; +} + +.container { + width: 574px; + height: 824px; + top: 18px; + gap: 20px; + margin: auto; + left: 688px; + background-color: var(--vscode-editor-background); +} + +.container em { + display: block; + text-align: justify; +} + +.button-theme-primary { + color: var(--vscode-button-foreground); + background: var(--vscode-button-background); + border: 1px solid var(--vscode-button-border); + padding: 8px 12px; +} +.button-theme-primary:hover:not(:disabled) { + background: var(--vscode-button-hoverBackground); + cursor: pointer; +} +.button-theme-secondary { + color: var(--vscode-button-secondaryForeground); + background: var(--vscode-button-secondaryBackground); + border: 1px solid var(--vscode-button-border); + padding: 8px 12px; +} +.button-theme-secondary:hover:not(:disabled) { + background: var(--vscode-button-secondaryHoverBackground); + cursor: pointer; +} + +.formfield { + display: flex; + align-items: center; + margin-bottom: 0.5rem; +} + +.payload-options-buttons { + display: flex; + align-items: center; + margin-top: 10px; + margin-bottom: 10px; +} + +.radio-selector { + width: 15px; + height: 15px; + border-radius: 50%; +} + +.label-selector { + padding-left: 7px; + font-weight: 500; + font-size: 13px; + line-height: 15.51px; + text-align: center; +} + +.form-row-select { + width: 387px; + height: 28px; + border: 1px; + border-radius: 5px; + gap: 4px; + padding: 2px 8px; +} + +.form-row-event-select { + width: 244px; + height: 28px; + margin-bottom: 15px; + margin-left: 8px; +} + +.runtime-description { + font-size: 12px; + margin-top: 5px; +} diff --git a/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts b/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts index c9b8f63a51e..643ea4631e2 100644 --- a/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts +++ b/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as fs from 'fs-extra' import * as path from 'path' import * as vscode from 'vscode' import * as nls from 'vscode-nls' @@ -36,9 +35,23 @@ import globals from '../../../shared/extensionGlobals' import { VueWebview } from '../../../webviews/main' import { Commands } from '../../../shared/vscode/commands2' import { telemetry } from '../../../shared/telemetry/telemetry' +import { fs } from '../../../shared' +import { ToolkitError } from '../../../shared' +import { ResourceNode } from '../../../awsService/appBuilder/explorer/nodes/resourceNode' const localize = nls.loadMessageBundle() +export interface ResourceData { + logicalId: string + region: string + arn: string + location: string + handler: string + runtime: string + stackName: string + source: string +} + export type AwsSamDebuggerConfigurationLoose = AwsSamDebuggerConfiguration & { invokeTarget: Omit< AwsSamDebuggerConfiguration['invokeTarget'], @@ -55,7 +68,7 @@ interface SampleQuickPickItem extends vscode.QuickPickItem { filename: string } -interface LaunchConfigPickItem extends vscode.QuickPickItem { +export interface LaunchConfigPickItem extends vscode.QuickPickItem { index: number config?: AwsSamDebuggerConfiguration } @@ -66,7 +79,8 @@ export class SamInvokeWebview extends VueWebview { public constructor( private readonly extContext: ExtContext, // TODO(sijaden): get rid of `ExtContext` - private readonly config?: AwsSamDebuggerConfiguration + private readonly config?: AwsSamDebuggerConfiguration, + private readonly data?: ResourceData ) { super(SamInvokeWebview.sourcePath) } @@ -79,11 +93,11 @@ export class SamInvokeWebview extends VueWebview { return this.config } - /** - * Open a quick pick containing the names of launch configs in the `launch.json` array. - * Filter out non-supported launch configs. - */ - public async loadSamLaunchConfig(): Promise { + public getResourceData() { + return this.data + } + + public async getSamLaunchConfigs(): Promise { // TODO: Find a better way to infer this. Might need another arg from the frontend (depends on the context in which the launch config is made?) const workspaceFolder = vscode.workspace.workspaceFolders?.length ? vscode.workspace.workspaceFolders[0] @@ -94,7 +108,17 @@ export class SamInvokeWebview extends VueWebview { } const uri = workspaceFolder.uri const launchConfig = new LaunchConfiguration(uri) - const pickerItems = await getLaunchConfigQuickPickItems(launchConfig, uri) + const pickerItems = await this.getLaunchConfigQuickPickItems(launchConfig, uri) + return pickerItems + } + + /** + * Open a quick pick containing the names of launch configs in the `launch.json` array. + * Filter out non-supported launch configs. + */ + public async loadSamLaunchConfig(): Promise { + const pickerItems: LaunchConfigPickItem[] = (await this.getSamLaunchConfigs()) || [] + if (pickerItems.length === 0) { pickerItems.push({ index: -1, @@ -151,9 +175,14 @@ export class SamInvokeWebview extends VueWebview { return sample } catch (err) { getLogger().error('Error getting manifest data..: %O', err as Error) + throw ToolkitError.chain(err, 'getting manifest data') } } + protected getTemplateRegistry() { + return globals.templateRegistry + } + /** * Get all templates in the registry. * Call back into the webview with the registry contents. @@ -161,7 +190,7 @@ export class SamInvokeWebview extends VueWebview { public async getTemplate() { const items: (vscode.QuickPickItem & { templatePath: string })[] = [] const noTemplate = 'NOTEMPLATEFOUND' - for (const template of (await globals.templateRegistry).items) { + for (const template of (await this.getTemplateRegistry()).items) { const resources = template.item.Resources if (resources) { for (const resource of Object.keys(resources)) { @@ -213,6 +242,41 @@ export class SamInvokeWebview extends VueWebview { } } + // This method serves as a wrapper around the backend function `openLaunchJsonFile`. + // The frontend cannot directly import and invoke backend functions like `openLaunchJsonFile` + // because doing so would break the webview environment by introducing server-side logic + // into client-side code. Instead, this method acts as an interface or bridge, allowing + // the frontend to request the backend to open the launch configuration file without + // directly coupling the frontend to backend-specific implementations. + public async openLaunchConfig() { + await openLaunchJsonFile() + } + + public async promptFile() { + const fileLocations = await vscode.window.showOpenDialog({ + openLabel: 'Open', + }) + + if (!fileLocations || fileLocations.length === 0) { + return undefined + } + + try { + const fileContent = await fs.readFileBytes(fileLocations[0].fsPath) + return { + sample: fileContent, + selectedFilePath: fileLocations[0].fsPath, + selectedFile: this.getFileName(fileLocations[0].fsPath), + } + } catch (e) { + getLogger().error('readFileSync: Failed to read file at path %s %O', fileLocations[0].fsPath, e) + throw ToolkitError.chain(e, 'Failed to read selected file') + } + } + + public getFileName(filePath: string): string { + return path.basename(filePath) + } /** * Open a quick pick containing the names of launch configs in the `launch.json` array, plus a "Create New Entry" entry. * On selecting a name, overwrite the existing entry in the `launch.json` array and resave the file. @@ -220,7 +284,7 @@ export class SamInvokeWebview extends VueWebview { * @param config Config to save */ public async saveLaunchConfig(config: AwsSamDebuggerConfiguration): Promise { - const uri = getUriFromLaunchConfig(config) + const uri = await this.getUriFromLaunchConfig(config) if (!uri) { // TODO Localize void vscode.window.showErrorMessage( @@ -228,13 +292,14 @@ export class SamInvokeWebview extends VueWebview { ) return } + const launchConfig = new LaunchConfiguration(uri) - const launchConfigItems = await getLaunchConfigQuickPickItems(launchConfig, uri) + const launchConfigItems = await this.getLaunchConfigQuickPickItems(launchConfig, uri) const pickerItems = [ { label: addCodiconToString( 'add', - localize('AWS.command.addSamDebugConfiguration', 'Add Debug Configuration') + localize('AWS.command.addSamDebugConfiguration', 'Add Local Invoke and Debug Configuration') ), index: -1, alwaysShow: true, @@ -267,7 +332,7 @@ export class SamInvokeWebview extends VueWebview { const response = await input.promptUser({ inputBox: ib }) if (response) { await launchConfig.addDebugConfiguration(finalizeConfig(config, response)) - await openLaunchJsonFile() + await this.openLaunchConfig() } } else { // use existing label @@ -275,7 +340,7 @@ export class SamInvokeWebview extends VueWebview { finalizeConfig(config, pickerResponse.label), pickerResponse.index ) - await openLaunchJsonFile() + await this.openLaunchConfig() } } @@ -284,12 +349,12 @@ export class SamInvokeWebview extends VueWebview { * TODO: Post validation failures back to webview? * @param config Config to invoke */ - public async invokeLaunchConfig(config: AwsSamDebuggerConfiguration): Promise { + public async invokeLaunchConfig(config: AwsSamDebuggerConfiguration, source?: string): Promise { const finalConfig = finalizeConfig( resolveWorkspaceFolderVariable(undefined, config), 'Editor-Created Debug Config' ) - const targetUri = getUriFromLaunchConfig(finalConfig) + const targetUri = await this.getUriFromLaunchConfig(finalConfig) const folder = targetUri ? vscode.workspace.getWorkspaceFolder(targetUri) : undefined // Cloud9 currently can't resolve the `aws-sam` debug config provider. @@ -298,12 +363,65 @@ export class SamInvokeWebview extends VueWebview { // (Cloud9 also doesn't currently have variable resolution support anyways) if (isCloud9()) { const provider = new SamDebugConfigProvider(this.extContext) - await provider.resolveDebugConfiguration(folder, finalConfig) + await provider.resolveDebugConfiguration(folder, finalConfig, undefined, source) } else { // startDebugging on VS Code goes through the whole resolution chain await vscode.debug.startDebugging(folder, finalConfig) } } + public async getLaunchConfigQuickPickItems( + launchConfig: LaunchConfiguration, + uri: vscode.Uri + ): Promise { + const existingConfigs = launchConfig.getDebugConfigurations() + const samValidator = new DefaultAwsSamDebugConfigurationValidator(vscode.workspace.getWorkspaceFolder(uri)) + const registry = await globals.templateRegistry + const mapped = existingConfigs.map((val, index) => { + return { + config: val as AwsSamDebuggerConfiguration, + index: index, + label: val.name, + } + }) + // XXX: can't use filter() with async predicate. + const filtered: LaunchConfigPickItem[] = [] + for (const c of mapped) { + const valid = await samValidator.validate(c.config, registry, true) + if (valid?.isValid) { + filtered.push(c) + } + } + return filtered + } + + public async getUriFromLaunchConfig(config: AwsSamDebuggerConfiguration): Promise { + let targetPath: string + if (isTemplateTargetProperties(config.invokeTarget)) { + targetPath = config.invokeTarget.templatePath + } else if (isCodeTargetProperties(config.invokeTarget)) { + targetPath = config.invokeTarget.projectRoot + } else { + // error + return undefined + } + if (path.isAbsolute(targetPath)) { + return vscode.Uri.file(targetPath) + } + // TODO: rework this logic (and config variables in general) + // we have too many places where we try to resolve these paths when it realistically can be + // in a single place. Much less bug-prone when it's centralized. + // the following line is a quick-fix for a very narrow edge-case + targetPath = targetPath.replace('${workspaceFolder}/', '') + const workspaceFolders = vscode.workspace.workspaceFolders || [] + for (const workspaceFolder of workspaceFolders) { + const absolutePath = tryGetAbsolutePath(workspaceFolder, targetPath) + if (await fs.exists(absolutePath)) { + return vscode.Uri.file(absolutePath) + } + } + + return undefined + } } const WebviewPanel = VueWebview.compilePanel(SamInvokeWebview) @@ -313,7 +431,7 @@ export function registerSamInvokeVueCommand(context: ExtContext): vscode.Disposa const webview = new WebviewPanel(context.extensionContext, context, launchConfig) await telemetry.sam_openConfigUi.run(async (span) => { await webview.show({ - title: localize('AWS.command.launchConfigForm.title', 'Edit SAM Debug Configuration'), + title: localize('AWS.command.launchConfigForm.title', 'Local Invoke and Debug Configuration'), // TODO: make this only open `Beside` when executed via CodeLens viewColumn: vscode.ViewColumn.Beside, }) @@ -321,58 +439,28 @@ export function registerSamInvokeVueCommand(context: ExtContext): vscode.Disposa }) } -function getUriFromLaunchConfig(config: AwsSamDebuggerConfiguration): vscode.Uri | undefined { - let targetPath: string - if (isTemplateTargetProperties(config.invokeTarget)) { - targetPath = config.invokeTarget.templatePath - } else if (isCodeTargetProperties(config.invokeTarget)) { - targetPath = config.invokeTarget.projectRoot - } else { - // error - return undefined - } - if (path.isAbsolute(targetPath)) { - return vscode.Uri.file(targetPath) - } - // TODO: rework this logic (and config variables in general) - // we have too many places where we try to resolve these paths when it realistically can be - // in a single place. Much less bug-prone when it's centralized. - // the following line is a quick-fix for a very narrow edge-case - targetPath = targetPath.replace('${workspaceFolder}/', '') - const workspaceFolders = vscode.workspace.workspaceFolders || [] - for (const workspaceFolder of workspaceFolders) { - const absolutePath = tryGetAbsolutePath(workspaceFolder, targetPath) - if (fs.pathExistsSync(absolutePath)) { - return vscode.Uri.file(absolutePath) - } - } - - return undefined -} - -async function getLaunchConfigQuickPickItems( - launchConfig: LaunchConfiguration, - uri: vscode.Uri -): Promise { - const existingConfigs = launchConfig.getDebugConfigurations() - const samValidator = new DefaultAwsSamDebugConfigurationValidator(vscode.workspace.getWorkspaceFolder(uri)) - const registry = await globals.templateRegistry - const mapped = existingConfigs.map((val, index) => { - return { - config: val as AwsSamDebuggerConfiguration, - index: index, - label: val.name, - } +export async function registerSamDebugInvokeVueCommand(context: ExtContext, params: { resource: ResourceNode }) { + const launchConfig: AwsSamDebuggerConfiguration | undefined = undefined + const resource = params?.resource.resource + const source = 'AppBuilderLocalInvoke' + const webview = new WebviewPanel(context.extensionContext, context, launchConfig, { + logicalId: resource.resource.Id ?? '', + region: resource.region ?? '', + location: resource.location.fsPath, + handler: resource.resource.Handler!, + runtime: resource.resource.Runtime!, + arn: resource.functionArn ?? '', + stackName: resource.stackName ?? '', + source: source, + }) + await telemetry.sam_openConfigUi.run(async (span) => { + telemetry.record({ source: 'AppBuilderDebugger' }), + await webview.show({ + title: localize('AWS.command.launchConfigForm.title', 'Local Invoke and Debug Configuration'), + // TODO: make this only open `Beside` when executed via CodeLens + viewColumn: vscode.ViewColumn.Beside, + }) }) - // XXX: can't use filter() with async predicate. - const filtered: LaunchConfigPickItem[] = [] - for (const c of mapped) { - const valid = await samValidator.validate(c.config, registry, true) - if (valid?.isValid) { - filtered.push(c) - } - } - return filtered } export function finalizeConfig(config: AwsSamDebuggerConfiguration, name: string): AwsSamDebuggerConfiguration { diff --git a/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue b/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue index 8d958ff8206..468d7393ac6 100644 --- a/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue +++ b/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue @@ -4,277 +4,388 @@ diff --git a/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts b/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts index 4bae01fd7dd..551be9d09cf 100644 --- a/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts +++ b/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts @@ -8,7 +8,12 @@ import { defineComponent } from 'vue' import { AwsSamDebuggerConfiguration } from '../../../shared/sam/debugger/awsSamDebugConfiguration' -import { AwsSamDebuggerConfigurationLoose, SamInvokeWebview } from './samInvokeBackend' +import { + AwsSamDebuggerConfigurationLoose, + LaunchConfigPickItem, + ResourceData, + SamInvokeWebview, +} from './samInvokeBackend' import settingsPanel from '../../../webviews/components/settingsPanel.vue' import { WebviewClientFactory } from '../../../webviews/client' import saveData from '../../../webviews/mixins/saveData' @@ -34,6 +39,15 @@ interface SamInvokeVueData { parameters: VueDataLaunchPropertyObject containerBuild: boolean skipNewImageCheck: boolean + selectedConfig: LaunchConfigPickItem + payloadOption: string + selectedFile: string + selectedFilePath: string + selectedTestEvent: string + TestEvents: string[] + showNameInput: boolean + newTestEventName: string + resourceData: ResourceData | undefined } function newLaunchConfig(existingConfig?: AwsSamDebuggerConfiguration): AwsSamDebuggerConfigurationLoose { @@ -105,6 +119,11 @@ function initData() { parameters: { value: '', errorMsg: '' }, headers: { value: '', errorMsg: '' }, stageVariables: { value: '', errorMsg: '' }, + selectedConfig: { index: 0, config: undefined, label: 'new-config' }, + selectedTestEvent: '', + TestEvents: [], + showNameInput: false, + newTestEventName: '', } } @@ -114,30 +133,7 @@ export default defineComponent({ settingsPanel, }, created() { - client.init().then( - (config) => this.parseConfig(config), - (e) => { - console.error('client.init failed: %s', (e as Error).message) - } - ) - - client.getRuntimes().then( - (runtimes) => { - this.runtimes = runtimes - }, - (e) => { - console.error('client.getRuntimes failed: %s', (e as Error).message) - } - ) - - client.getCompanyName().then( - (o) => { - this.company = o - }, - (e) => { - console.error('client.getCompanyName failed: %s', (e as Error).message) - } - ) + this.setUpWebView() }, mixins: [saveData], data(): SamInvokeVueData { @@ -152,6 +148,10 @@ export default defineComponent({ ], runtimes: [], httpMethods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH'], + payloadOption: 'sampleEvents', + selectedFile: '', + selectedFilePath: '', + resourceData: undefined, } }, methods: { @@ -161,12 +161,18 @@ export default defineComponent({ this.headers.errorMsg = '' this.stageVariables.errorMsg = '' }, - launch() { + async launch() { const config = this.formatConfig() - config && - client.invokeLaunchConfig(config).catch((e) => { - console.error('invokeLaunchConfig failed: %s', (e as Error).message) - }) + + if (!config) { + return // Exit early if config is not available + } + + const source = this.resourceData?.source + + client.invokeLaunchConfig(config, source).catch((e: Error) => { + console.error(`invokeLaunchConfig failed: ${e.message}`) + }) }, save() { const config = this.formatConfig() @@ -183,16 +189,34 @@ export default defineComponent({ } ) }, - parseConfig(config?: AwsSamDebuggerConfiguration) { + async parseConfig(config?: AwsSamDebuggerConfiguration) { if (!config) { return } const company = this.company this.clearForm() this.launchConfig = newLaunchConfig(config) + if (config.lambda?.payload) { this.payload.value = JSON.stringify(config.lambda.payload.json, undefined, 4) } + + const localArgs = config.sam?.localArguments + + if (!localArgs && this.payload.value) { + this.payloadOption = 'sampleEvents' + this.selectedFile = '' + } + + if (localArgs?.includes('-e') || localArgs?.includes('--event')) { + const index = localArgs.findIndex((arg) => arg === '-e' || arg === '--event') + + if (index !== -1 && localArgs[index + 1]) { + this.payloadOption = 'localFile' + this.selectedFile = await client.getFileName(localArgs[index + 1]) + } + } + if (config.lambda?.environmentVariables) { this.environmentVariables.value = JSON.stringify(config.lambda?.environmentVariables) } @@ -244,7 +268,7 @@ export default defineComponent({ if (!field) { return undefined } - //Reg ex for a comma with 0 or more whitespace before and/or after + // Reg ex for a comma with 0 or more whitespace before and/or after const re = /\s*,\s*/g return field.trim().split(re) }, @@ -259,6 +283,95 @@ export default defineComponent({ } } }, + async openLaunchJson() { + await client.openLaunchConfig() + }, + onFileChange(event: Event) { + const input = event.target as HTMLInputElement + if (input.files && input.files.length > 0) { + const file = input.files[0] + this.selectedFile = file.name + + // Use Blob.text() to read the file as text + file.text() + .then((text) => { + this.payload.value = text + }) + .catch((error) => { + console.error('Error reading file:', error) + }) + } + }, + async promptForFileLocation() { + const resp = await client.promptFile() + + if (resp) { + this.selectedFile = resp.selectedFile + this.launchConfig.sam = this.launchConfig.sam || {} + this.launchConfig.sam.localArguments = this.launchConfig.sam.localArguments || [] + + // Ensure only one '-e ' or '--event ' exists + const eventArgIndex = this.launchConfig.sam.localArguments.findIndex( + (arg) => arg === '-e' || arg === '--event' + ) + + if (eventArgIndex !== -1 && this.launchConfig.sam.localArguments[eventArgIndex + 1]) { + // Replace the existing file path for either '-e' or '--event' + this.launchConfig.sam.localArguments[eventArgIndex + 1] = resp.selectedFilePath + } else { + // Add '-e ' if not already present + this.launchConfig.sam.localArguments.push('-e', resp.selectedFilePath) + } + } + }, + showNameField() { + this.showNameInput = true + }, + setUpWebView() { + client.init().then( + (config) => this.parseConfig(config), + (e) => { + console.error('client.init failed: %s', (e as Error).message) + } + ) + + if (this.launchConfig.invokeTarget.templatePath === '') { + client.getResourceData().then( + (data) => { + this.resourceData = data + if (this.launchConfig && this.resourceData) { + this.launchConfig.invokeTarget.logicalId = this.resourceData.logicalId + this.launchConfig.invokeTarget.templatePath = this.resourceData.location + this.launchConfig.invokeTarget.lambdaHandler = this.resourceData.handler + if (this.launchConfig.lambda) { + this.launchConfig.lambda.runtime = this.resourceData.runtime + } + } + }, + (e) => { + console.error('client.getResourceData failed: %s', (e as Error).message) + } + ) + } + + client.getRuntimes().then( + (runtimes) => { + this.runtimes = runtimes + }, + (e) => { + console.error('client.getRuntimes failed: %s', (e as Error).message) + } + ) + + client.getCompanyName().then( + (o) => { + this.company = o + }, + (e) => { + console.error('client.getCompanyName failed: %s', (e as Error).message) + } + ) + }, formatConfig() { this.resetJsonErrors() @@ -279,6 +392,27 @@ export default defineComponent({ // propagate those through to the `postMessage` command, causing an error. We can stop // this by recursively accessing all primitive fields (which is what this line does) const launchConfig: AwsSamDebuggerConfigurationLoose = JSON.parse(JSON.stringify(this.launchConfig)) + const localArgs = launchConfig.sam?.localArguments + + const removeEventArg = () => { + if (localArgs) { + const eventArgIndex = localArgs?.findIndex((arg) => arg === '-e' || arg === '--event') + if (eventArgIndex !== -1) { + // Remove the event argument and its value + localArgs?.splice(eventArgIndex, 2) + } + } + } + + if (localArgs) { + if (this.payload && this.payloadOption !== 'localFile') { + removeEventArg() + } else if (this.payloadOption === 'localFile' && this.selectedFile) { + payloadJson = undefined + } else { + removeEventArg() + } + } return { ...launchConfig, @@ -314,7 +448,9 @@ export default defineComponent({ }, clearForm() { const init = initData() - Object.keys(init).forEach((k) => (this.$data[k as keyof typeof init] = init[k as keyof typeof init] as any)) + Object.keys(init).forEach((k) => { + ;(this as any)[k] = init[k as keyof typeof init] + }) }, }, }) diff --git a/packages/core/src/lambda/vue/remoteInvoke/invokeLambda.ts b/packages/core/src/lambda/vue/remoteInvoke/invokeLambda.ts index 852537800f8..501304c1a94 100644 --- a/packages/core/src/lambda/vue/remoteInvoke/invokeLambda.ts +++ b/packages/core/src/lambda/vue/remoteInvoke/invokeLambda.ts @@ -4,10 +4,11 @@ */ import { _Blob } from 'aws-sdk/clients/lambda' -import { readFileSync } from 'fs' +import { readFileSync } from 'fs' // eslint-disable-line no-restricted-imports import * as _ from 'lodash' import * as vscode from 'vscode' import { DefaultLambdaClient, LambdaClient } from '../../../shared/clients/lambdaClient' +import * as picker from '../../../shared/ui/picker' import { ExtContext } from '../../../shared/extensions' import { getLogger } from '../../../shared/logger' @@ -18,16 +19,35 @@ import { getSampleLambdaPayloads, SampleRequest } from '../../utils' import * as nls from 'vscode-nls' import { VueWebview } from '../../../webviews/main' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { Result } from '../../../shared/telemetry/telemetry' +import { telemetry, Result } from '../../../shared/telemetry/telemetry' +import { decodeBase64 } from '../../../shared' +import { + runSamCliRemoteTestEvents, + SamCliRemoteTestEventsParameters, + TestEventsOperation, +} from '../../../shared/sam/cli/samCliRemoteTestEvent' +import { getSamCliContext } from '../../../shared/sam/cli/samCliContext' +import { ToolkitError } from '../../../shared' +import { basename } from 'path' const localize = nls.loadMessageBundle() +type Event = { + name: string + region: string + arn: string + event?: string +} + export interface InitialData { FunctionName: string FunctionArn: string FunctionRegion: string InputSamples: SampleRequest[] + TestEvents?: string[] + Source?: string + StackName?: string + LogicalId?: string } export interface RemoteInvokeData { @@ -35,6 +55,15 @@ export interface RemoteInvokeData { selectedSampleRequest: string sampleText: string selectedFile: string + selectedFilePath: string + selectedTestEvent: string + payload: string + showNameInput: boolean + newTestEventName: string + selectedFunction: string +} +interface SampleQuickPickItem extends vscode.QuickPickItem { + filename: string } export class RemoteInvokeWebview extends VueWebview { @@ -53,7 +82,7 @@ export class RemoteInvokeWebview extends VueWebview { return this.data } - public async invokeLambda(input: string): Promise { + public async invokeLambda(input: string, source?: string): Promise { let result: Result = 'Succeeded' this.channel.show() @@ -61,7 +90,7 @@ export class RemoteInvokeWebview extends VueWebview { try { const funcResponse = await this.client.invoke(this.data.FunctionArn, input) - const logs = funcResponse.LogResult ? Buffer.from(funcResponse.LogResult, 'base64').toString() : '' + const logs = funcResponse.LogResult ? decodeBase64(funcResponse.LogResult) : '' const payload = funcResponse.Payload ? funcResponse.Payload : JSON.stringify({}) this.channel.appendLine(`Invocation result for ${this.data.FunctionArn}`) @@ -78,17 +107,10 @@ export class RemoteInvokeWebview extends VueWebview { this.channel.appendLine('') result = 'Failed' } finally { - telemetry.lambda_invokeRemote.emit({ result, passive: false }) + telemetry.lambda_invokeRemote.emit({ result, passive: false, source: source }) } } - public async getSample(requestName: string) { - const sampleUrl = `${sampleRequestPath}${requestName}` - const sample = (await new HttpResourceFetcher(sampleUrl, { showUrl: true }).get()) ?? '' - - return sample - } - public async promptFile() { const fileLocations = await vscode.window.showOpenDialog({ openLabel: 'Open', @@ -100,14 +122,109 @@ export class RemoteInvokeWebview extends VueWebview { try { const fileContent = readFileSync(fileLocations[0].fsPath, { encoding: 'utf8' }) + return { + sample: fileContent, + selectedFilePath: fileLocations[0].fsPath, + selectedFile: this.getFileName(fileLocations[0].fsPath), + } + } catch (e) { + getLogger().error('readFileSync: Failed to read file at path %s %O', fileLocations[0].fsPath, e) + throw ToolkitError.chain(e, 'Failed to read selected file') + } + } + + public async loadFile(fileLocations: string) { + return await this.readFile(fileLocations) + } + + private async readFile(filePath: string) { + if (!filePath) { + return undefined + } + const fileLocation = vscode.Uri.file(filePath) + try { + const fileContent = readFileSync(fileLocation.fsPath, { encoding: 'utf8' }) return { sample: fileContent, - selectedFile: fileLocations[0].path, + selectedFilePath: fileLocation.fsPath, + selectedFile: this.getFileName(fileLocation.fsPath), } } catch (e) { - getLogger().error('readFileSync: Failed to read file at path %O', fileLocations[0].fsPath, e) - void vscode.window.showErrorMessage((e as Error).message) + getLogger().error('readFileSync: Failed to read file at path %s %O', fileLocation.fsPath, e) + throw ToolkitError.chain(e, 'Failed to read selected file') + } + } + + private getFileName(filePath: string): string { + return basename(filePath) + } + + public async listRemoteTestEvents(functionArn: string, region: string): Promise { + const params: SamCliRemoteTestEventsParameters = { + functionArn: functionArn, + operation: TestEventsOperation.List, + region: region, + } + const result = await this.remoteTestEvents(params) + return result.split('\n') + } + + public async createRemoteTestEvents(putEvent: Event) { + const params: SamCliRemoteTestEventsParameters = { + functionArn: putEvent.arn, + operation: TestEventsOperation.Put, + name: putEvent.name, + eventSample: putEvent.event, + region: putEvent.region, + } + return await this.remoteTestEvents(params) + } + public async getRemoteTestEvents(getEvents: Event) { + const params: SamCliRemoteTestEventsParameters = { + name: getEvents.name, + operation: TestEventsOperation.Get, + functionArn: getEvents.arn, + region: getEvents.region, + } + return await this.remoteTestEvents(params) + } + + private async remoteTestEvents(params: SamCliRemoteTestEventsParameters) { + return await runSamCliRemoteTestEvents(params, getSamCliContext().invoker) + } + + public async getSamplePayload(): Promise { + try { + const inputs: SampleQuickPickItem[] = (await getSampleLambdaPayloads()).map((entry) => { + return { label: entry.name ?? '', filename: entry.filename ?? '' } + }) + + const qp = picker.createQuickPick({ + items: inputs, + options: { + title: localize( + 'AWS.lambda.form.pickSampleInput', + 'Enter keywords to filter the list of sample events' + ), + }, + }) + + const choices = await picker.promptUser({ + picker: qp, + }) + const pickerResponse = picker.verifySinglePickerOutput(choices) + + if (!pickerResponse) { + return + } + const sampleUrl = `${sampleRequestPath}${pickerResponse.filename}` + const sample = (await new HttpResourceFetcher(sampleUrl, { showUrl: true }).get()) ?? '' + + return sample + } catch (err) { + getLogger().error('Error getting manifest data..: %O', err as Error) + throw ToolkitError.chain(err, 'getting manifest data') } } } @@ -125,17 +242,23 @@ export async function invokeRemoteLambda( */ outputChannel: vscode.OutputChannel functionNode: LambdaFunctionNode + source?: string } ) { const inputs = await getSampleLambdaPayloads() - const client = new DefaultLambdaClient(params.functionNode.regionCode) - + const resource: any = params.functionNode + const source: string = params.source || 'AwsExplorerRemoteInvoke' + const client = new DefaultLambdaClient(resource.regionCode) const wv = new Panel(context.extensionContext, context.outputChannel, client, { - FunctionName: params.functionNode.configuration.FunctionName ?? '', - FunctionArn: params.functionNode.configuration.FunctionArn ?? '', - FunctionRegion: params.functionNode.regionCode, + FunctionName: resource.configuration.FunctionName ?? '', + FunctionArn: resource.configuration.FunctionArn ?? '', + FunctionRegion: resource.regionCode, InputSamples: inputs, + TestEvents: [], + Source: source, }) - await wv.show({ title: localize('AWS.invokeLambda.title', 'Invoke Lambda {0}', params.functionNode.functionName) }) + await wv.show({ + title: localize('AWS.invokeLambda.title', 'Invoke Lambda {0}', resource.configuration.FunctionName), + }) } diff --git a/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.css b/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.css new file mode 100644 index 00000000000..99f124e6b0c --- /dev/null +++ b/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.css @@ -0,0 +1,132 @@ +.Icontainer { + margin-inline: auto; + margin-top: 5rem; +} + +h1 { + text-align: left; + margin-bottom: 20px; +} + +div { + width: 521px; +} + +.form-row { + display: grid; + grid-template-columns: 150px 1fr; + margin-bottom: 10px; +} +.form-row-select { + width: 387px; + height: 28px; + border: 1px; + border-radius: 5px; + gap: 4px; + padding: 2px 8px; +} + +.dynamic-span { + white-space: nowrap; + text-overflow: initial; + overflow: scroll; + width: 381px; + height: 28px; + font-weight: 500; + font-size: 13px; + line-height: 15.51px; +} + +.form-row-event-select { + width: 244px; + height: 28px; + margin-bottom: 15px; + margin-left: 8px; +} + +.payload-options { + display: grid; + grid-template-columns: 150px 1fr; + align-items: center; + margin-bottom: 10px; +} + +label { + margin-right: 10px; +} + +span, +select, +.payload-options { + display: block; +} + +textarea { + color: var(--vscode-settings-textInputForeground); + background: var(--vscode-settings-textInputBackground); + border: 1px solid var(--vscode-settings-textInputBorder); +} + +.payload-options-button { + display: grid; + align-items: center; + border: none; + padding: 5px 10px; + cursor: pointer; + font-size: 0.9em; + margin-bottom: 10px; +} + +.button-theme-primary { + color: var(--vscode-button-foreground); + background: var(--vscode-button-background); + border: 1px solid var(--vscode-button-border); + padding: 8px 12px; +} +.button-theme-primary:hover:not(:disabled) { + background: var(--vscode-button-hoverBackground); + cursor: pointer; +} +.button-theme-secondary { + color: var(--vscode-button-secondaryForeground); + background: var(--vscode-button-secondaryBackground); + border: 1px solid var(--vscode-button-border); + padding: 8px 12px; +} +.button-theme-secondary:hover:not(:disabled) { + background: var(--vscode-button-secondaryHoverBackground); + cursor: pointer; +} + +.payload-options-buttons { + display: flex; + align-items: center; + margin-top: 10px; + margin-bottom: 10px; +} + +.radio-selector { + width: 15px; + height: 15px; + border-radius: 50%; +} + +.label-selector { + padding-left: 7px; + font-weight: 500; + font-size: 13px; + line-height: 15.51px; + text-align: center; +} + +.form-row-select { + display: grid; + grid-template-columns: 150px 1fr; + margin-bottom: 10px; +} + +.formfield { + display: flex; + align-items: center; + margin-bottom: 0.5rem; +} diff --git a/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.vue b/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.vue index a6e0d70d0f3..9e06ef590ba 100644 --- a/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.vue +++ b/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.vue @@ -1,75 +1,128 @@ /*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -
${column}