diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 95e5ff6cfb5d6..f9f73f419d7d0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -49,8 +49,6 @@ Please take a moment to review the [project readme](https://github.com/woocommer - Please create a change file for your changes by running `pnpm --filter= changelog add`. For example, a change file for the WooCommerce Core project would be added by running `pnpm --filter=@woocommerce/plugin-woocommerce changelog add`. - Please avoid modifying the changelog directly or updating the .pot files. These will be updated by the WooCommerce team. -If you are contributing code to our (Javascript-driven) Gutenberg blocks, please note that they are developed in their [own repository](https://github.com/woocommerce/woocommerce-gutenberg-products-block) and have their [own issue tracker](https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues). - ## Feature Requests 🚀 The best place to submit feature requests is over on our [dedicated feature request page](https://woocommerce.com/feature-requests/woocommerce/). You can easily search and vote for existing requests, or create new requests if necessary. diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.yml b/.github/ISSUE_TEMPLATE/1-bug-report.yml index 2e429ca9290d3..e711f09cc3028 100644 --- a/.github/ISSUE_TEMPLATE/1-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/1-bug-report.yml @@ -13,7 +13,6 @@ body: While our goal is to address all the issues reported in this repository, GitHub should be treated as a place to report confirmed bugs only. - If you have a support request or custom code related question please follow one of the steps below: - Review [WooCommerce Self-Service Guide](https://woocommerce.com/document/woocommerce-self-service-guide/) to see if the solutions listed there apply to your case; - - If you are a paying customer of WooCommerce, contact WooCommerce support by [opening a ticket or starting a live chat](https://woocommerce.com/contact-us/); - Make a post on [WooCommerce community forum](https://wordpress.org/support/plugin/woocommerce/) - To get help on custom code questions go to the [WooCommerce Community Slack](https://woocommerce.com/community-slack/) and visit the `#developers` channel. diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml index fd3fc4a381a8c..c9519aeb27776 100644 --- a/.github/workflows/cherry-pick.yml +++ b/.github/workflows/cherry-pick.yml @@ -26,9 +26,9 @@ on: env: GIT_COMMITTER_NAME: 'WooCommerce Bot' - GIT_COMMITTER_EMAIL: 'no-reply@woo.com' + GIT_COMMITTER_EMAIL: 'no-reply@woocommerce.com' GIT_AUTHOR_NAME: 'WooCommerce Bot' - GIT_AUTHOR_EMAIL: 'no-reply@woo.com' + GIT_AUTHOR_EMAIL: 'no-reply@woocommerce.com' permissions: {} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1130db6126143..297f23cc47e76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,374 +1,392 @@ name: 'CI' on: - pull_request: - push: - branches: - - 'trunk' - - 'release/*' + pull_request: + push: + branches: + - 'trunk' + - 'release/*' concurrency: - group: '${{ github.workflow }}-${{ github.ref }}' - cancel-in-progress: true + group: '${{ github.workflow }}-${{ github.ref }}' + cancel-in-progress: true jobs: - project-jobs: - # Since this is a monorepo, not every pull request or change is going to impact every project. - # Instead of running CI tasks on all projects indiscriminately, we use a command to detect - # which projects have changed and what kind of change occurred. This lets us build the - # matrices that we can use to run CI tasks only on the projects that need them. - name: 'Build Project Jobs' - runs-on: 'ubuntu-20.04' - outputs: - lint-jobs: ${{ steps.project-jobs.outputs.lint-jobs }} - default-test-jobs: ${{ steps.project-jobs.outputs.default-test-jobs }} - e2e-test-jobs: ${{ steps.project-jobs.outputs.e2e-test-jobs }} - api-test-jobs: ${{ steps.project-jobs.outputs.api-test-jobs }} - performance-test-jobs: ${{ steps.project-jobs.outputs.performance-test-jobs }} - steps: - - uses: 'actions/checkout@v4' - name: 'Checkout' - with: - fetch-depth: 0 - - uses: './.github/actions/setup-woocommerce-monorepo' - name: 'Setup Monorepo' - with: - php-version: false # We don't want to waste time installing PHP since we aren't using it in this job. - - uses: actions/github-script@v6 - name: 'Build Matrix' - id: 'project-jobs' - with: - script: | - let baseRef = ${{ toJson( github.base_ref ) }}; - if ( baseRef ) { - baseRef = `--base-ref origin/${ baseRef }`; - } - const child_process = require( 'node:child_process' ); - child_process.execSync( `pnpm utils ci-jobs ${ baseRef }` ); - - project-lint-jobs: - name: 'Lint - ${{ matrix.projectName }}' - runs-on: 'ubuntu-20.04' - needs: 'project-jobs' - if: ${{ needs.project-jobs.outputs.lint-jobs != '[]' }} - strategy: - fail-fast: false - matrix: - include: ${{ fromJSON( needs.project-jobs.outputs.lint-jobs ) }} - steps: - - uses: 'actions/checkout@v4' - name: 'Checkout' - with: - fetch-depth: 0 - - uses: './.github/actions/setup-woocommerce-monorepo' - name: 'Setup Monorepo' - id: 'setup-monorepo' - with: - install: '${{ matrix.projectName }}...' - build: '${{ matrix.projectName }}' - - name: 'Lint' - run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' - - project-default-test-jobs: - name: 'Test - ${{ matrix.projectName }} - ${{ matrix.name }}' - runs-on: 'ubuntu-20.04' - needs: 'project-jobs' - if: ${{ needs.project-jobs.outputs.default-test-jobs != '[]' }} - strategy: - fail-fast: false - matrix: - include: ${{ fromJSON( needs.project-jobs.outputs.default-test-jobs ) }} - steps: - - uses: 'actions/checkout@v4' - name: 'Checkout' - - uses: './.github/actions/setup-woocommerce-monorepo' - name: 'Setup Monorepo' - id: 'setup-monorepo' - with: - install: '${{ matrix.projectName }}...' - build: '${{ matrix.projectName }}' - - name: 'Prepare Test Environment' - id: 'prepare-test-environment' - if: ${{ matrix.testEnv.shouldCreate }} - env: ${{ matrix.testEnv.envVars }} - run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}' - - name: 'Test' - run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' - - project-e2e-test-jobs: - name: 'E2E - ${{ matrix.name }}' - runs-on: 'ubuntu-20.04' - needs: 'project-jobs' - if: ${{ needs.project-jobs.outputs.e2e-test-jobs != '[]' }} - strategy: - fail-fast: false - matrix: - include: ${{ fromJSON( needs.project-jobs.outputs.e2e-test-jobs ) }} - steps: - - uses: 'actions/checkout@v4' - name: 'Checkout' - - - uses: './.github/actions/setup-woocommerce-monorepo' - name: 'Setup Monorepo' - id: 'setup-monorepo' - with: - install: '${{ matrix.projectName }}...' - build: '${{ matrix.projectName }}' - - - name: 'Prepare Test Environment' - id: 'prepare-test-environment' - if: ${{ matrix.testEnv.shouldCreate }} - run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}' - - - name: 'Run tests' - run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' - - - name: 'Upload artifacts' - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: all-blob-reports-${{ matrix.shardNumber }} - path: ${{ matrix.projectPath }}/tests/e2e-pw/test-results/allure-results - retention-days: 1 - compression-level: 9 - - project-api-test-jobs: - name: 'API - ${{ matrix.name }}' - runs-on: 'ubuntu-20.04' - needs: 'project-jobs' - if: ${{ needs.project-jobs.outputs.api-test-jobs != '[]' }} - strategy: - fail-fast: false - matrix: - include: ${{ fromJSON( needs.project-jobs.outputs.api-test-jobs ) }} - steps: - - uses: 'actions/checkout@v4' - name: 'Checkout' - - - uses: './.github/actions/setup-woocommerce-monorepo' - name: 'Setup Monorepo' - id: 'setup-monorepo' - with: - install: '${{ matrix.projectName }}...' - build: '${{ matrix.projectName }}' - - - name: 'Prepare Test Environment' - id: 'prepare-test-environment' - if: ${{ matrix.testEnv.shouldCreate }} - run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}' - - - name: 'Run tests' - run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' - - - name: 'Upload artifacts' - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: all-blob-reports-${{ matrix.shardNumber }} - path: ${{ matrix.projectPath }}/tests/api-core-tests/test-results/allure-results - retention-days: 1 - compression-level: 9 - - project-performance-test-jobs: - name: 'Performance - ${{ matrix.name }}' - runs-on: 'ubuntu-20.04' - needs: 'project-jobs' - if: ${{ needs.project-jobs.outputs.performance-test-jobs != '[]' }} - strategy: - fail-fast: false - matrix: - include: ${{ fromJSON( needs.project-jobs.outputs.performance-test-jobs ) }} - steps: - - uses: 'actions/checkout@v4' - name: 'Checkout' - - - uses: './.github/actions/setup-woocommerce-monorepo' - name: 'Setup Monorepo' - id: 'setup-monorepo' - with: - install: '${{ matrix.projectName }}...' - build: '${{ matrix.projectName }}' - - - name: 'Prepare Test Environment' - id: 'prepare-test-environment' - if: ${{ matrix.testEnv.shouldCreate }} - run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}' - - - name: 'Run tests' - run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' - - evaluate-project-jobs: - # In order to add a required status check we need a consistent job that we can grab onto. - # Since we are dynamically generating a matrix for the project jobs, however, we can't - # rely on any specific job being present. We can get around this limitation by - # using a job that runs after all the others and either passes or fails based - # on the results of the other jobs in the workflow. - name: 'Evaluate Project Job Statuses' - runs-on: 'ubuntu-20.04' - needs: [ - 'project-jobs', - 'project-lint-jobs', - 'project-default-test-jobs', - 'project-e2e-test-jobs', - 'project-api-test-jobs' - ] - if: ${{ always() }} - steps: - - name: 'Evaluation' - run: | - result="${{ needs.project-jobs.result }}" - if [[ $result != "success" && $result != "skipped" ]]; then - echo "An error occurred generating the CI jobs." - exit 1 - fi - result="${{ needs.project-lint-jobs.result }}" - if [[ $result != "success" && $result != "skipped" ]]; then - echo "One or more lint jobs have failed." - exit 1 - fi - result="${{ needs.project-default-test-jobs.result }}" - if [[ $result != "success" && $result != "skipped" ]]; then - echo "One or more test jobs have failed." - exit 1 - fi - result="${{ needs.project-e2e-test-jobs.result }}" - if [[ $result != "success" && $result != "skipped" ]]; then - echo "One or more e2e test jobs have failed." - exit 1 - fi - result="${{ needs.project-api-test-jobs.result }}" - if [[ $result != "success" && $result != "skipped" ]]; then - echo "One or more api test jobs have failed." - exit 1 - fi - echo "All jobs have completed successfully." - - e2e-test-reports: - name: 'Report e2e tests results' - needs: [ project-e2e-test-jobs ] - if: ${{ ! cancelled() && needs.project-e2e-test-jobs.result != 'skipped' }} - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: 'Install Allure CLI' - env: - DESTINATION_PATH: ../ - run: ./.github/workflows/scripts/install-allure.sh - - - name: 'Download blob reports from artifacts' - uses: actions/download-artifact@v4 - with: - path: ./out/allure-results - pattern: all-blob-reports-* - run-id: project-e2e-test-jobs - merge-multiple: true - - - name: 'Generate Allure report' - id: generate_allure_report - run: allure generate --clean ./out/allure-results --output ./out/allure-report - - - name: 'Archive reports' - uses: actions/upload-artifact@v4 - with: - name: e2e-test-report - path: ./out - if-no-files-found: ignore - retention-days: 5 - - - name: 'Send workflow dispatch' - env: - GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - RUN_ID: ${{ github.run_id }} - run: | - if [ "$GITHUB_EVENT_NAME" == pull_request ]; then - gh workflow run publish-test-reports-pr.yml \ - -f run_id=$RUN_ID \ - -f e2e_artifact=e2e-test-report \ - -f pr_number=$PR_NUMBER \ - -f commit_sha=$GITHUB_SHA \ - -f s3_root=public \ - --repo woocommerce/woocommerce-test-reports - else - gh workflow run publish-test-reports-trunk-merge.yml \ - -f run_id=$RUN_ID \ - -f artifact=e2e-test-report \ - -f pr_number=$PR_NUMBER \ - -f commit_sha=$GITHUB_SHA \ - -f test_type="e2e" \ - --repo woocommerce/woocommerce-test-reports - fi - - - name: 'Send Slack notification' - if: github.event_name != 'pull_request' - uses: automattic/action-test-results-to-slack@v0.3.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - slack_token: ${{ secrets.E2E_SLACK_TOKEN }} - slack_channel: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }} - - api-test-reports: - name: 'Report API tests results' - needs: [ project-api-test-jobs ] - if: ${{ ! cancelled() && needs.project-api-test-jobs.result != 'skipped'}} - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: 'Install Allure CLI' - env: - DESTINATION_PATH: ../ - run: ./.github/workflows/scripts/install-allure.sh - - - name: 'Download blob reports from artifacts' - uses: actions/download-artifact@v4 - with: - path: ./out/allure-results - pattern: all-blob-reports-* - run-id: project-api-test-jobs - merge-multiple: true - - - name: 'Generate Allure report' - id: generate_allure_report - run: allure generate --clean ./out/allure-results --output ./out/allure-report - - - name: 'Archive reports' - uses: actions/upload-artifact@v4 - with: - name: api-test-report - path: ./out - if-no-files-found: ignore - retention-days: 5 - - - name: 'Publish reports' + project-jobs: + # Since this is a monorepo, not every pull request or change is going to impact every project. + # Instead of running CI tasks on all projects indiscriminately, we use a command to detect + # which projects have changed and what kind of change occurred. This lets us build the + # matrices that we can use to run CI tasks only on the projects that need them. + name: 'Build Project Jobs' + runs-on: 'ubuntu-20.04' + outputs: + lint-jobs: ${{ steps.project-jobs.outputs.lint-jobs }} + default-test-jobs: ${{ steps.project-jobs.outputs.default-test-jobs }} + e2e-test-jobs: ${{ steps.project-jobs.outputs.e2e-test-jobs }} + api-test-jobs: ${{ steps.project-jobs.outputs.api-test-jobs }} + performance-test-jobs: ${{ steps.project-jobs.outputs.performance-test-jobs }} + steps: + - uses: 'actions/checkout@v4' + name: 'Checkout' + with: + fetch-depth: 0 + - uses: './.github/actions/setup-woocommerce-monorepo' + name: 'Setup Monorepo' + with: + php-version: false # We don't want to waste time installing PHP since we aren't using it in this job. + - uses: actions/github-script@v7 + name: 'Build Matrix' + id: 'project-jobs' + with: + script: | + let baseRef = ${{ toJson( github.base_ref ) }}; + if ( baseRef ) { + baseRef = `--base-ref origin/${ baseRef }`; + } + + let githubEvent = ${{ toJson( github.event_name ) }}; + + const child_process = require( 'node:child_process' ); + child_process.execSync( `pnpm utils ci-jobs ${ baseRef } --event ${ githubEvent }` ); + + project-lint-jobs: + name: 'Lint - ${{ matrix.projectName }}' + runs-on: 'ubuntu-20.04' + needs: 'project-jobs' + if: ${{ needs.project-jobs.outputs.lint-jobs != '[]' }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON( needs.project-jobs.outputs.lint-jobs ) }} + steps: + - uses: 'actions/checkout@v4' + name: 'Checkout' + with: + fetch-depth: 0 + - uses: './.github/actions/setup-woocommerce-monorepo' + name: 'Setup Monorepo' + id: 'setup-monorepo' + with: + install: '${{ matrix.projectName }}...' + build: '${{ matrix.projectName }}' + - name: 'Lint' + run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' + + project-default-test-jobs: + name: 'Test - ${{ matrix.projectName }} - ${{ matrix.name }}' + runs-on: 'ubuntu-20.04' + needs: 'project-jobs' + if: ${{ needs.project-jobs.outputs.default-test-jobs != '[]' }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON( needs.project-jobs.outputs.default-test-jobs ) }} + steps: + - uses: 'actions/checkout@v4' + name: 'Checkout' + - uses: './.github/actions/setup-woocommerce-monorepo' + name: 'Setup Monorepo' + id: 'setup-monorepo' + with: + install: '${{ matrix.projectName }}...' + build: '${{ matrix.projectName }}' + - name: 'Prepare Test Environment' + id: 'prepare-test-environment' + if: ${{ matrix.testEnv.shouldCreate }} + env: ${{ matrix.testEnv.envVars }} + run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}' + - name: 'Test' + run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' + + project-e2e-test-jobs: + name: 'E2E - ${{ matrix.name }}' + runs-on: 'ubuntu-20.04' + needs: 'project-jobs' + if: ${{ needs.project-jobs.outputs.e2e-test-jobs != '[]' }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON( needs.project-jobs.outputs.e2e-test-jobs ) }} + steps: + - uses: 'actions/checkout@v4' + name: 'Checkout' + + - uses: './.github/actions/setup-woocommerce-monorepo' + name: 'Setup Monorepo' + id: 'setup-monorepo' + with: + install: '${{ matrix.projectName }}...' + build: '${{ matrix.projectName }}' + + - name: 'Prepare Test Environment' + id: 'prepare-test-environment' + if: ${{ matrix.testEnv.shouldCreate }} + run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}' + + - name: 'Run tests' + run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' + + - name: 'Upload artifacts' + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: all-blob-e2e-reports-${{ strategy.job-index }} + path: ${{ matrix.projectPath }}/tests/e2e-pw/test-results + retention-days: 1 + compression-level: 9 + + project-api-test-jobs: + name: 'API - ${{ matrix.name }}' + runs-on: 'ubuntu-20.04' + needs: 'project-jobs' + if: ${{ needs.project-jobs.outputs.api-test-jobs != '[]' }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON( needs.project-jobs.outputs.api-test-jobs ) }} + steps: + - uses: 'actions/checkout@v4' + name: 'Checkout' + + - uses: './.github/actions/setup-woocommerce-monorepo' + name: 'Setup Monorepo' + id: 'setup-monorepo' + with: + install: '${{ matrix.projectName }}...' + build: '${{ matrix.projectName }}' + + - name: 'Prepare Test Environment' + id: 'prepare-test-environment' + if: ${{ matrix.testEnv.shouldCreate }} + run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}' + + - name: 'Run tests' + run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' + + - name: 'Upload artifacts' + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: all-blob-api-reports-${{ strategy.job-index }} + path: ${{ matrix.projectPath }}/tests/api-core-tests/test-results/allure-results + retention-days: 1 + compression-level: 9 + + project-performance-test-jobs: + name: 'Performance - ${{ matrix.name }}' + runs-on: 'ubuntu-20.04' + needs: 'project-jobs' + if: ${{ needs.project-jobs.outputs.performance-test-jobs != '[]' }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON( needs.project-jobs.outputs.performance-test-jobs ) }} env: - GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - RUN_ID: ${{ github.run_id }} - run: | - if [ "$GITHUB_EVENT_NAME" == pull_request ]; then - gh workflow run publish-test-reports-pr.yml \ - -f run_id=$RUN_ID \ - -f api_artifact=api-test-report \ - -f pr_number=$PR_NUMBER \ - -f commit_sha=$GITHUB_SHA \ - -f s3_root=public \ - --repo woocommerce/woocommerce-test-reports - else - gh workflow run publish-test-reports-trunk-merge.yml \ - -f run_id=$RUN_ID \ - -f artifact=api-test-report \ - -f pr_number=$PR_NUMBER \ - -f commit_sha=$GITHUB_SHA \ - -f test_type="api" \ - --repo woocommerce/woocommerce-test-reports - fi - - - name: 'Send Slack notification' - if: github.event_name != 'pull_request' - uses: automattic/action-test-results-to-slack@v0.3.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - slack_token: ${{ secrets.E2E_SLACK_TOKEN }} - slack_channel: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }} + WP_ARTIFACTS_PATH: ${{ github.workspace }}/artifacts + + steps: + - uses: 'actions/checkout@v4' + name: 'Checkout' + + - uses: './.github/actions/setup-woocommerce-monorepo' + name: 'Setup Monorepo' + id: 'setup-monorepo' + with: + install: '${{ matrix.projectName }}...' + build: '${{ matrix.projectName }}' + + - name: 'Prepare Test Environment' + id: 'prepare-test-environment' + if: ${{ matrix.testEnv.shouldCreate }} + run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}' + + - name: 'Run tests' + env: + CODEVITALS_PROJECT_TOKEN: ${{ secrets.CODEVITALS_PROJECT_TOKEN }} + run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' + + - name: 'Archive metrics results' + if: ${{ success() && matrix.name == 'Metrics' }} + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: metrics-results + path: ${{ env.WP_ARTIFACTS_PATH }}/*.performance-results*.json + + evaluate-project-jobs: + # In order to add a required status check we need a consistent job that we can grab onto. + # Since we are dynamically generating a matrix for the project jobs, however, we can't + # rely on any specific job being present. We can get around this limitation by + # using a job that runs after all the others and either passes or fails based + # on the results of the other jobs in the workflow. + name: 'Evaluate Project Job Statuses' + runs-on: 'ubuntu-20.04' + needs: + [ + 'project-jobs', + 'project-lint-jobs', + 'project-default-test-jobs', + 'project-e2e-test-jobs', + 'project-api-test-jobs', + ] + if: ${{ always() }} + steps: + - name: 'Evaluation' + run: | + result="${{ needs.project-jobs.result }}" + if [[ $result != "success" && $result != "skipped" ]]; then + echo "An error occurred generating the CI jobs." + exit 1 + fi + result="${{ needs.project-lint-jobs.result }}" + if [[ $result != "success" && $result != "skipped" ]]; then + echo "One or more lint jobs have failed." + exit 1 + fi + result="${{ needs.project-default-test-jobs.result }}" + if [[ $result != "success" && $result != "skipped" ]]; then + echo "One or more test jobs have failed." + exit 1 + fi + result="${{ needs.project-e2e-test-jobs.result }}" + if [[ $result != "success" && $result != "skipped" ]]; then + echo "One or more e2e test jobs have failed." + exit 1 + fi + result="${{ needs.project-api-test-jobs.result }}" + if [[ $result != "success" && $result != "skipped" ]]; then + echo "One or more api test jobs have failed." + exit 1 + fi + echo "All jobs have completed successfully." + + e2e-test-reports: + name: 'Report e2e tests results' + needs: [project-e2e-test-jobs] + if: ${{ ! cancelled() && needs.project-e2e-test-jobs.result != 'skipped' }} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: 'Install Allure CLI' + env: + DESTINATION_PATH: ../ + run: ./.github/workflows/scripts/install-allure.sh + + - name: 'Download blob reports from artifacts' + uses: actions/download-artifact@v4 + with: + path: ./out + pattern: all-blob-e2e-reports-* + run-id: project-e2e-test-jobs + merge-multiple: true + + - name: 'Generate Allure report' + id: generate_allure_report + run: allure generate --clean ./out/allure-results --output ./out/allure-report + + - name: 'Archive reports' + uses: actions/upload-artifact@v4 + with: + name: e2e-test-report + path: ./out + if-no-files-found: ignore + retention-days: 5 + + - name: 'Send workflow dispatch' + env: + GH_TOKEN: ${{ secrets.REPORTS_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + RUN_ID: ${{ github.run_id }} + run: | + if [ "$GITHUB_EVENT_NAME" == pull_request ]; then + gh workflow run publish-test-reports-pr.yml \ + -f run_id=$RUN_ID \ + -f e2e_artifact=e2e-test-report \ + -f pr_number=$PR_NUMBER \ + -f commit_sha=$GITHUB_SHA \ + -f s3_root=public \ + --repo woocommerce/woocommerce-test-reports + else + gh workflow run publish-test-reports-trunk-merge.yml \ + -f run_id=$RUN_ID \ + -f artifact=e2e-test-report \ + -f pr_number=$PR_NUMBER \ + -f commit_sha=$GITHUB_SHA \ + -f test_type="e2e" \ + --repo woocommerce/woocommerce-test-reports + fi + + - name: 'Send Slack notification' + if: github.event_name != 'pull_request' + uses: automattic/action-test-results-to-slack@v0.3.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + slack_token: ${{ secrets.E2E_SLACK_TOKEN }} + slack_channel: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }} + playwright_report_path: ./out/test-results-*.json + playwright_output_dir: ./out/results-data + + api-test-reports: + name: 'Report API tests results' + needs: [project-api-test-jobs] + if: ${{ ! cancelled() && needs.project-api-test-jobs.result != 'skipped'}} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: 'Install Allure CLI' + env: + DESTINATION_PATH: ../ + run: ./.github/workflows/scripts/install-allure.sh + + - name: 'Download blob reports from artifacts' + uses: actions/download-artifact@v4 + with: + path: ./out/allure-results + pattern: all-blob-api-reports-* + run-id: project-api-test-jobs + merge-multiple: true + + - name: 'Generate Allure report' + id: generate_allure_report + run: allure generate --clean ./out/allure-results --output ./out/allure-report + + - name: 'Archive reports' + uses: actions/upload-artifact@v4 + with: + name: api-test-report + path: ./out + if-no-files-found: ignore + retention-days: 5 + + - name: 'Publish reports' + env: + GH_TOKEN: ${{ secrets.REPORTS_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + RUN_ID: ${{ github.run_id }} + run: | + if [ "$GITHUB_EVENT_NAME" == pull_request ]; then + gh workflow run publish-test-reports-pr.yml \ + -f run_id=$RUN_ID \ + -f api_artifact=api-test-report \ + -f pr_number=$PR_NUMBER \ + -f commit_sha=$GITHUB_SHA \ + -f s3_root=public \ + --repo woocommerce/woocommerce-test-reports + else + gh workflow run publish-test-reports-trunk-merge.yml \ + -f run_id=$RUN_ID \ + -f artifact=api-test-report \ + -f pr_number=$PR_NUMBER \ + -f commit_sha=$GITHUB_SHA \ + -f test_type="api" \ + --repo woocommerce/woocommerce-test-reports + fi + + - name: 'Send Slack notification' + if: github.event_name != 'pull_request' + uses: automattic/action-test-results-to-slack@v0.3.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + slack_token: ${{ secrets.E2E_SLACK_TOKEN }} + slack_channel: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }} diff --git a/.github/workflows/community-label.yml b/.github/workflows/community-label.yml index 3361dfa033bad..1f8151c66407a 100644 --- a/.github/workflows/community-label.yml +++ b/.github/workflows/community-label.yml @@ -1,4 +1,4 @@ -name: Add Community Label +name: Add Community Label, Assign Reviewers on: pull_request_target: @@ -38,7 +38,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: 'If community PR, assign a reviewer' + - name: 'Assign reviewers' if: github.event.pull_request && steps.check.outputs.is-community == 'yes' uses: shufo/auto-assign-reviewer-by-files@f5f3db9ef06bd72ab6978996988c6462cbdaabf6 with: diff --git a/.github/workflows/metrics.yml b/.github/workflows/deprecated/metrics.yml similarity index 100% rename from .github/workflows/metrics.yml rename to .github/workflows/deprecated/metrics.yml diff --git a/.github/workflows/non-cot-e2e-tests.yml b/.github/workflows/deprecated/non-cot-e2e-tests.yml similarity index 99% rename from .github/workflows/non-cot-e2e-tests.yml rename to .github/workflows/deprecated/non-cot-e2e-tests.yml index 383bdc4fcbad6..04768429408b0 100644 --- a/.github/workflows/non-cot-e2e-tests.yml +++ b/.github/workflows/deprecated/non-cot-e2e-tests.yml @@ -1,4 +1,4 @@ -name: Run tests against PR in an environment with HPOS disabled +name: Run tests with HPOS disabled on: push: branches: diff --git a/.github/workflows/non-hpos-build-and-e2e-tests-daily.yml b/.github/workflows/deprecated/non-hpos-build-and-e2e-tests-daily.yml similarity index 99% rename from .github/workflows/non-hpos-build-and-e2e-tests-daily.yml rename to .github/workflows/deprecated/non-hpos-build-and-e2e-tests-daily.yml index b28e9134ec382..e3bb5afd6bd51 100644 --- a/.github/workflows/non-hpos-build-and-e2e-tests-daily.yml +++ b/.github/workflows/deprecated/non-hpos-build-and-e2e-tests-daily.yml @@ -32,7 +32,7 @@ jobs: working-directory: plugins/woocommerce env: ENABLE_HPOS: 0 - run: pnpm --filter=@woocommerce/plugin-woocommerce env:test:cot + run: pnpm --filter=@woocommerce/plugin-woocommerce env:test:no-hpos - name: Download and install Chromium browser. working-directory: plugins/woocommerce @@ -91,7 +91,7 @@ jobs: working-directory: plugins/woocommerce env: ENABLE_HPOS: 0 - run: pnpm --filter=@woocommerce/plugin-woocommerce env:test:cot + run: pnpm --filter=@woocommerce/plugin-woocommerce env:test:no-hpos - name: Run Playwright API tests. id: run_playwright_api_tests diff --git a/.github/workflows/pr-build-and-e2e-tests.yml b/.github/workflows/deprecated/pr-build-and-e2e-tests.yml similarity index 99% rename from .github/workflows/pr-build-and-e2e-tests.yml rename to .github/workflows/deprecated/pr-build-and-e2e-tests.yml index 4edd57092fc74..4f5c94626953d 100644 --- a/.github/workflows/pr-build-and-e2e-tests.yml +++ b/.github/workflows/deprecated/pr-build-and-e2e-tests.yml @@ -1,9 +1,9 @@ name: Run tests against PR on: workflow_dispatch: - #pull_request: - #paths-ignore: - #- '**/changelog/**' + pull_request: + paths-ignore: + - '**/changelog/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/smoke-test-daily-site-check.yml b/.github/workflows/deprecated/smoke-test-daily-site-check.yml similarity index 100% rename from .github/workflows/smoke-test-daily-site-check.yml rename to .github/workflows/deprecated/smoke-test-daily-site-check.yml diff --git a/.github/workflows/smoke-test-pr-merge.yml b/.github/workflows/deprecated/smoke-test-pr-merge.yml similarity index 99% rename from .github/workflows/smoke-test-pr-merge.yml rename to .github/workflows/deprecated/smoke-test-pr-merge.yml index 991cbef0aa603..39b85581c43d3 100644 --- a/.github/workflows/smoke-test-pr-merge.yml +++ b/.github/workflows/deprecated/smoke-test-pr-merge.yml @@ -1,9 +1,9 @@ name: Run tests against trunk after PR merge on: workflow_dispatch: - #pull_request: - #types: - #- closed + pull_request: + types: + - closed concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml index 75717ffb62e5d..6f2b9bb284b99 100644 --- a/.github/workflows/post-release.yml +++ b/.github/workflows/post-release.yml @@ -5,9 +5,9 @@ on: env: GIT_COMMITTER_NAME: 'WooCommerce Bot' - GIT_COMMITTER_EMAIL: 'no-reply@woo.com' + GIT_COMMITTER_EMAIL: 'no-reply@woocommerce.com' GIT_AUTHOR_NAME: 'WooCommerce Bot' - GIT_AUTHOR_EMAIL: 'no-reply@woo.com' + GIT_AUTHOR_EMAIL: 'no-reply@woocommerce.com' permissions: {} diff --git a/.github/workflows/release-code-freeze.yml b/.github/workflows/release-code-freeze.yml index 7224674f5cb4e..6bf892968fab2 100644 --- a/.github/workflows/release-code-freeze.yml +++ b/.github/workflows/release-code-freeze.yml @@ -16,9 +16,9 @@ on: env: TIME_OVERRIDE: ${{ inputs.timeOverride || 'now' }} GIT_COMMITTER_NAME: 'WooCommerce Bot' - GIT_COMMITTER_EMAIL: 'no-reply@woo.com' + GIT_COMMITTER_EMAIL: 'no-reply@woocommerce.com' GIT_AUTHOR_NAME: 'WooCommerce Bot' - GIT_AUTHOR_EMAIL: 'no-reply@woo.com' + GIT_AUTHOR_EMAIL: 'no-reply@woocommerce.com' permissions: {} diff --git a/.github/workflows/scripts/run-metrics.sh b/.github/workflows/scripts/run-metrics.sh new file mode 100755 index 0000000000000..4bbe85ee47014 --- /dev/null +++ b/.github/workflows/scripts/run-metrics.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -eo pipefail + +if [[ -z "$GITHUB_EVENT_NAME" ]]; then + echo "::error::GITHUB_EVENT_NAME must be set" + exit 1 +fi + +if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then + echo "Comparing performance with trunk" + pnpm --filter="compare-perf" run compare perf $GITHUB_SHA trunk --tests-branch $GITHUB_SHA + +elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then + echo "Comparing performance with base branch" + # The base hash used here need to be a commit that is compatible with the current WP version + # The current one is 19f3d0884617d7ecdcf37664f648a51e2987cada + # it needs to be updated every time it becomes unsupported by the current wp-env (WP version). + # It is used as a base comparison point to avoid fluctuation in the performance metrics. + WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt) + echo "WP_VERSION: $WP_VERSION" + IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION" + WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" + pnpm --filter="compare-perf" run compare perf $GITHUB_SHA 19f3d0884617d7ecdcf37664f648a51e2987cada --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" + + echo "Publish results to CodeVitals" + COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI") + pnpm --filter="compare-perf" run log $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA 19f3d0884617d7ecdcf37664f648a51e2987cada $COMMITTED_AT +else + echo "Unsupported event: $GITHUB_EVENT_NAME" +fi diff --git a/.github/workflows/smoke-test-daily.yml b/.github/workflows/smoke-test-daily.yml index cd6368bef1f46..93f25860e9779 100644 --- a/.github/workflows/smoke-test-daily.yml +++ b/.github/workflows/smoke-test-daily.yml @@ -11,7 +11,7 @@ env: PLUGIN_SLACK_BLOCKS_ARTIFACT: plugin-blocks concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: '${{ github.workflow }}-${{ github.ref }}' cancel-in-progress: true permissions: {} @@ -80,7 +80,7 @@ jobs: e2e-tests: name: E2E tests on nightly build runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 90 strategy: fail-fast: false matrix: @@ -120,11 +120,11 @@ jobs: run: pnpm exec playwright install chromium - name: Run E2E tests - timeout-minutes: 60 + timeout-minutes: 90 id: run_playwright_e2e_tests env: USE_WP_ENV: 1 - E2E_MAX_FAILURES: 15 + E2E_MAX_FAILURES: 90 FORCE_COLOR: 1 ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results @@ -237,13 +237,17 @@ jobs: ./k6 run plugins/woocommerce/tests/performance/tests/gh-action-daily-ext-requests.js test-plugins: - name: Smoke tests on trunk with ${{ matrix.plugin }} plugin installed + name: E2E tests with ${{ matrix.plugin }} plugin installed runs-on: ubuntu-latest permissions: contents: read env: ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report + PLUGIN_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }} + PLUGIN_NAME: ${{ matrix.plugin }} + PLUGIN_SLUG: ${{ matrix.slug }} + GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }} strategy: fail-fast: false matrix: @@ -257,16 +261,12 @@ jobs: - plugin: 'WooCommerce Shipping & Tax' repo: 'automattic/woocommerce-services' slug: woocommerce-services - - plugin: 'Woo Subscriptions' - repo: WC_SUBSCRIPTIONS_REPO - private: true - slug: woocommerce-subscriptions - plugin: 'Gutenberg' repo: 'WordPress/gutenberg' slug: gutenberg - plugin: 'Gutenberg - Nightly' repo: 'bph/gutenberg' - slug: gutenberg-nightly + slug: gutenberg steps: - uses: actions/checkout@v3 @@ -280,28 +280,28 @@ jobs: uses: ./.github/actions/tests/setup-local-test-environment with: test-type: e2e + + - name: Setup plugin for the main e2e suite + working-directory: ./plugins/woocommerce + run: ./tests/e2e-pw/bin/install-plugin.sh - name: Run 'Upload plugin' test id: run-upload-plugin-test + if: ${{ failure() }} # only if the plugin setup failed to check if the plugin really cannot be installed uses: ./.github/actions/tests/run-e2e-tests with: report-name: Smoke tests on trunk with ${{ matrix.plugin }} plugin installed (run ${{ github.run_number }}) tests: upload-plugin.spec.js - env: - PLUGIN_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }} - PLUGIN_NAME: ${{ matrix.plugin }} - PLUGIN_SLUG: ${{ matrix.slug }} - GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }} - + - name: Run the rest of E2E tests id: run-e2e-composite-action - timeout-minutes: 60 + timeout-minutes: 90 uses: ./.github/actions/tests/run-e2e-tests with: playwright-config: ignore-plugin-tests.playwright.config.js report-name: Smoke tests on trunk with ${{ matrix.plugin }} plugin installed (run ${{ github.run_number }}) env: - E2E_MAX_FAILURES: 15 + E2E_MAX_FAILURES: 90 - name: Create context block and save as JSON file if: success() || failure() @@ -403,8 +403,6 @@ jobs: slug: woocommerce-paypal-payments - plugin: 'WooCommerce Shipping & Tax' slug: woocommerce-services - - plugin: 'Woo Subscriptions' - slug: woocommerce-subscriptions - plugin: 'Gutenberg' slug: gutenberg - plugin: 'Gutenberg - Nightly' diff --git a/.github/workflows/smoke-test-release.yml b/.github/workflows/smoke-test-release.yml index f9d3c49f4fb5c..f961eb3cc4ec0 100644 --- a/.github/workflows/smoke-test-release.yml +++ b/.github/workflows/smoke-test-release.yml @@ -58,7 +58,7 @@ jobs: - name: Run E2E tests id: run-e2e-composite-action - timeout-minutes: 60 + timeout-minutes: 90 uses: ./.github/actions/tests/run-e2e-tests with: report-name: ${{ env.E2E_UPDATE_WC_ARTIFACT }} @@ -206,7 +206,7 @@ jobs: - name: Run E2E tests id: run-e2e-composite-action - timeout-minutes: 60 + timeout-minutes: 90 uses: ./.github/actions/tests/run-e2e-tests with: report-name: e2e-wp-latest--partial--run-${{ github.run_number }} @@ -219,7 +219,7 @@ jobs: CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }} CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }} DEFAULT_TIMEOUT_OVERRIDE: 120000 - E2E_MAX_FAILURES: 25 + E2E_MAX_FAILURES: 90 RESET_SITE: true - name: Download 'e2e-update-wc' artifact @@ -383,10 +383,10 @@ jobs: - name: Run E2E tests id: run-e2e-composite-action - timeout-minutes: 60 + timeout-minutes: 90 uses: ./.github/actions/tests/run-e2e-tests env: - E2E_MAX_FAILURES: 15 + E2E_MAX_FAILURES: 90 ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }} ALLURE_REPORT_DIR: ${{ env.E2E_ALLURE_REPORT_DIR }} DEFAULT_TIMEOUT_OVERRIDE: 120000 @@ -532,13 +532,13 @@ jobs: - name: Run E2E tests id: run-e2e-composite-action - timeout-minutes: 60 + timeout-minutes: 90 uses: ./.github/actions/tests/run-e2e-tests env: ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }} ALLURE_REPORT_DIR: ${{ env.E2E_ALLURE_REPORT_DIR }} DEFAULT_TIMEOUT_OVERRIDE: 120000 - E2E_MAX_FAILURES: 15 + E2E_MAX_FAILURES: 90 with: report-name: ${{ env.E2E_ARTIFACT }} @@ -650,7 +650,7 @@ jobs: - name: Run 'Upload plugin' test id: run-upload-test - timeout-minutes: 60 + timeout-minutes: 90 uses: ./.github/actions/tests/run-e2e-tests with: report-name: ${{ env.ARTIFACT_NAME }} @@ -662,13 +662,13 @@ jobs: - name: Run the rest of E2E tests id: run-e2e-composite-action - timeout-minutes: 60 + timeout-minutes: 90 uses: ./.github/actions/tests/run-e2e-tests with: playwright-config: ignore-plugin-tests.playwright.config.js report-name: ${{ env.ARTIFACT_NAME }} env: - E2E_MAX_FAILURES: 15 + E2E_MAX_FAILURES: 90 - name: Upload Allure artifacts to bucket if: | diff --git a/.github/workflows/stalebot.yml b/.github/workflows/stalebot.yml index 2150d5847a195..f74aee6730e06 100644 --- a/.github/workflows/stalebot.yml +++ b/.github/workflows/stalebot.yml @@ -1,32 +1,45 @@ name: 'Process stale needs-feedback issues' on: - schedule: - - cron: '21 0 * * *' + schedule: + - cron: '21 0 * * *' permissions: {} jobs: - stale: - runs-on: ubuntu-20.04 - permissions: - contents: read - issues: write - pull-requests: write - steps: - - name: Scan issues - uses: actions/stale@v8 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: "As a part of this repository's maintenance, this issue is being marked as stale due to inactivity. Please feel free to comment on it in case we missed something.\n\n###### After 7 days with no activity this issue will be automatically be closed." - close-issue-message: 'This issue was closed because it has been 14 days with no activity.' - operations-per-run: 140 - days-before-stale: -1 - days-before-close: -1 - days-before-issue-stale: 7 - days-before-issue-close: 7 - stale-issue-label: 'status: stale' - stale-pr-label: 'status: stale' - exempt-issue-labels: 'type: enhancement' - only-issue-labels: 'needs: author feedback' - close-issue-label: "status: can't reproduce" - ascending: true + stale: + runs-on: ubuntu-20.04 + permissions: + contents: read + issues: write + pull-requests: write + steps: + - name: Scan issues + uses: actions/stale@v9.0.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: "As a part of this repository's maintenance, this issue is being marked as stale due to inactivity. Please feel free to comment on it in case we missed something.\n\n###### After 7 days with no activity this issue will be automatically be closed." + close-issue-message: 'This issue was closed because it has been 14 days with no activity.' + operations-per-run: 140 + days-before-stale: -1 + days-before-close: -1 + days-before-issue-stale: 7 + days-before-issue-close: 7 + stale-issue-label: 'status: stale' + stale-pr-label: 'status: stale' + exempt-issue-labels: 'type: enhancement' + only-issue-labels: 'needs: author feedback' + close-issue-label: "status: can't reproduce" + ascending: true + - name: Close Stale Flaky Test Issues + uses: actions/stale@v9.0.0 + with: + only-labels: 'metric: flaky e2e test, team: Vortex' + days-before-stale: 5 + days-before-close: 7 + stale-issue-label: 'metric: stale flaky e2e test report' + stale-issue-message: 'This test may have just been a one-time failure. It will be auto-closed if no further activity occurs within the next 2 days.' + close-issue-message: 'Auto-closed due to inactivity. Please re-open if you believe this issue is still valid.' + close-issue-reason: 'not_planned' + remove-stale-when-updated: true + exempt-all-assignees: true + enable-statistics: true diff --git a/.github/workflows/test-assistant-api-rest-change-tracker.yml b/.github/workflows/test-assistant-api-rest-change-tracker.yml index dd9b2052b0f79..ca65da6a9c413 100644 --- a/.github/workflows/test-assistant-api-rest-change-tracker.yml +++ b/.github/workflows/test-assistant-api-rest-change-tracker.yml @@ -40,9 +40,61 @@ jobs: - name: Determine Milestone Date id: get_milestone_date +<<<<<<< HEAD run: ./scripts/determine_milestone_date.sh env: GITHUB_EVENT_PATH_PULL_REQUEST_MILESTONE_TITLE: ${{ github.event.pull_request.milestone.title }} +======= + run: | + #!/bin/bash + + MILESTONE_TITLE="${{ github.event.pull_request.milestone.title }}" + MILESTONE_DATE="Undefined" + + # Mapping of milestone titles to release dates + declare -A MILESTONE_DATES + MILESTONE_DATES=( + ["8.0.0"]="2023-08-08" + ["8.1.0"]="2023-09-12" + ["8.2.0"]="2023-10-10" + ["8.3.0"]="2023-11-14" + ["8.4.0"]="2023-12-12" + ["8.5.0"]="2024-01-09" + ["8.6.0"]="2024-02-13" + ["8.7.0"]="2024-03-12" + ["8.8.0"]="2024-04-09" + ["8.9.0"]="2024-05-14" + ["9.0.0"]="2024-06-11" + ["9.1.0"]="2024-07-09" + ["9.2.0"]="2024-08-13" + ["9.3.0"]="2024-09-10" + ["9.4.0"]="2024-10-08" + ["9.5.0"]="2024-11-12" + ["9.6.0"]="2024-12-10" + ["9.7.0"]="2025-01-14" + ["9.8.0"]="2025-02-11" + ["9.9.0"]="2025-03-11" + ["10.0.0"]="2025-04-08" + ["10.1.0"]="2025-05-13" + ["10.2.0"]="2025-06-10" + ["10.3.0"]="2025-07-08" + ["10.4.0"]="2025-08-12" + ["10.5.0"]="2025-09-09" + ["10.6.0"]="2025-10-14" + ["10.7.0"]="2025-11-11" + ["10.8.0"]="2025-12-09" + ["10.9.0"]="2026-01-13" + ["11.0.0"]="2026-02-10" + ) + + # Check if the milestone title exists in our predefined list and get the date + if [[ -v "MILESTONE_DATES[${MILESTONE_TITLE}]" ]]; then + MILESTONE_DATE=${MILESTONE_DATES[${MILESTONE_TITLE}]} + fi + + # Export for later steps + echo "MILESTONE_DATE=${MILESTONE_DATE}" >> $GITHUB_ENV +>>>>>>> origin/trunk # Notify Slack Step - name: Notify Slack diff --git a/.github/workflows/test-assistant-release-highlight-tracker.yml b/.github/workflows/test-assistant-release-highlight-tracker.yml index fb2399cf29aa8..5ac53d4b0a2da 100644 --- a/.github/workflows/test-assistant-release-highlight-tracker.yml +++ b/.github/workflows/test-assistant-release-highlight-tracker.yml @@ -60,6 +60,26 @@ jobs: ["8.8.0"]="2024-04-09" ["8.9.0"]="2024-05-14" ["9.0.0"]="2024-06-11" + ["9.1.0"]="2024-07-09" + ["9.2.0"]="2024-08-13" + ["9.3.0"]="2024-09-10" + ["9.4.0"]="2024-10-08" + ["9.5.0"]="2024-11-12" + ["9.6.0"]="2024-12-10" + ["9.7.0"]="2025-01-14" + ["9.8.0"]="2025-02-11" + ["9.9.0"]="2025-03-11" + ["10.0.0"]="2025-04-08" + ["10.1.0"]="2025-05-13" + ["10.2.0"]="2025-06-10" + ["10.3.0"]="2025-07-08" + ["10.4.0"]="2025-08-12" + ["10.5.0"]="2025-09-09" + ["10.6.0"]="2025-10-14" + ["10.7.0"]="2025-11-11" + ["10.8.0"]="2025-12-09" + ["10.9.0"]="2026-01-13" + ["11.0.0"]="2026-02-10" ) # Check if the milestone title exists in our predefined list and get the date diff --git a/.github/workflows/triage-replies.yml b/.github/workflows/triage-replies.yml index 795791c021ebe..b9d253be4706e 100644 --- a/.github/workflows/triage-replies.yml +++ b/.github/workflows/triage-replies.yml @@ -68,8 +68,6 @@ jobs: troubleshooting is done. In order to confirm the bug, please follow one of the steps below:\n\n\ - Review [WooCommerce Self-Service Guide](https://woocommerce.com/document/woocommerce-self-service-guide/) \ to see if the solutions listed there apply to your case;\n\ - - If you are a paying customer of WooCommerce, contact WooCommerce support by \ - [opening a ticket or starting a live chat](https://woocommerce.com/contact-us/);\n\ - Make a post on [WooCommerce community forum](https://wordpress.org/support/plugin/woocommerce/)\n\n\ If you confirm the bug, please provide us with clear steps to reproduce it.\n\n\ We are closing this issue for now as it seems to be a support request and not a bug. \ diff --git a/.nvmrc b/.nvmrc index 6f7f377bf5148..9a2a0e219c9b2 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v16 +v20 diff --git a/.syncpackrc b/.syncpackrc index e8f638ee3f7b1..63cb82b045c8c 100644 --- a/.syncpackrc +++ b/.syncpackrc @@ -24,7 +24,7 @@ "dependencyTypes": [ "engines" ], - "pinVersion": "^16.14.1", + "pinVersion": "^20.11.1", "packages": [ "**" ] @@ -245,6 +245,15 @@ ], "pinVersion": "^2.3.2" }, + { + "dependencies": [ + "glob" + ], + "packages": [ + "**" + ], + "pinVersion": "^10.3.10" + }, { "dependencies": [ "postcss-loader" @@ -263,6 +272,15 @@ ], "pinVersion": "^8.4.32" }, + { + "dependencies": [ + "rimraf" + ], + "packages": [ + "**" + ], + "pinVersion": "5.0.5" + }, { "dependencies": [ "sass-loader" @@ -290,6 +308,15 @@ ], "pinVersion": "^14.16.1" }, + { + "dependencies": [ + "uuid" + ], + "packages": [ + "**" + ], + "pinVersion": "^9.0.1" + }, { "dependencies": [ "@types/node" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ca72e50b48088..879213cd31d17 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [support@woo.com](mailto:support@woo.com). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [support@woocommerce.com](mailto:support@woocommerce.com). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. diff --git a/changelog.txt b/changelog.txt index 689d9c110cc79..c688314f74353 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,213 @@ == Changelog == += 8.8.3 2024-04-29 = + +* Update - Reverts auto-injecting specific Woo Blocks in every block theme and restores only auto-injecting in themes found in the allow list. [#46935](https://github.com/woocommerce/woocommerce/pull/46935) + += 8.8.2 2024-04-17 = + +* Fix - Fixed a bug causing incompatibility with 3rd-party coupon extensions when certain conditions were met. [#46642](https://github.com/woocommerce/woocommerce/pull/46642) +* Fix - Defensive coding for when Action Scheduler function as_has_scheduled_action is not defined. [#46630](https://github.com/woocommerce/woocommerce/pull/46630) + += 8.8.1 2024-04-15 = + +* Fix - Fix wrong Shop title shown in classic themes after deleting the page [#46429](https://github.com/woocommerce/woocommerce/pull/46429) + += 8.8.0 2024-04-10 = + +* Fix - Deprecate the $check_key_exists parameter from AssetDataRegistry and disallow duplicate data for all cases. [#46139](https://github.com/woocommerce/woocommerce/pull/46139) +* Fix - Fixed an issue where orders could be placed when no shipping options were available [#46026](https://github.com/woocommerce/woocommerce/pull/46026) +* Fix - Fix a bug where saved payment methods were not rendered correctly in the heckout block [#46019](https://github.com/woocommerce/woocommerce/pull/46019) +* Fix - Removed count from is_array check to fix Analytics comparison filter. [#45939](https://github.com/woocommerce/woocommerce/pull/45939) +* Fix - Add a filter to adjust the 50 terms limitation in the product edit page. [#45506](https://github.com/woocommerce/woocommerce/pull/45506) +* Fix - Add block preview to Product Filter: Attribute (Beta) block [#45558](https://github.com/woocommerce/woocommerce/pull/45558) +* Fix - Add some safeguards against programmatic removal of orders due to sync when HPOS is active. [#45330](https://github.com/woocommerce/woocommerce/pull/45330) +* Fix - Adds spacing between quantity field and add to cart button when stacked [#45758](https://github.com/woocommerce/woocommerce/pull/45758) +* Fix - Adjust the WC_Admin_Notices to support multisite setups [#45349](https://github.com/woocommerce/woocommerce/pull/45349) +* Fix - Avoid trying to find a product variation of a product variation [#45776](https://github.com/woocommerce/woocommerce/pull/45776) +* Fix - CYS - Add missing typography settings for the Site Title block [#45166](https://github.com/woocommerce/woocommerce/pull/45166) +* Fix - CYS - Core: fix: not mark `Customize your store` step as completed when the user switches theme [#45762](https://github.com/woocommerce/woocommerce/pull/45762) +* Fix - CYS - Core: fix font load when user opts out of tracking. [#45185](https://github.com/woocommerce/woocommerce/pull/45185) +* Fix - CYS - Core: fix Product Rating block renders [#45600](https://github.com/woocommerce/woocommerce/pull/45600) +* Fix - CYS - Core: fix wp-admin page visible when click on start designing [#45586](https://github.com/woocommerce/woocommerce/pull/45586) +* Fix - CYS - Core: install font when user clicks opt-in [#45580](https://github.com/woocommerce/woocommerce/pull/45580) +* Fix - CYS - Fix activeThemeHasMods undefined error. [#45255](https://github.com/woocommerce/woocommerce/pull/45255) +* Fix - CYS - Fix the failed to load resource error in the CYS whenever the current active theme is not TT4 [#45519](https://github.com/woocommerce/woocommerce/pull/45519) +* Fix - CYS - Fix the flickering effect on hover on the font pairing cards. [#44851](https://github.com/woocommerce/woocommerce/pull/44851) +* Fix - CYS - Fix the intro page logo and site title positioning. [#45216](https://github.com/woocommerce/woocommerce/pull/45216) +* Fix - CYS - Fix the selected pattern in footer in the assembler. [#45240](https://github.com/woocommerce/woocommerce/pull/45240) +* Fix - CYS - fix warning Tooltip [#45592](https://github.com/woocommerce/woocommerce/pull/45592) +* Fix - CYS - Go to the assembler when clicking to the "Design you own" button if the theme was already customized in the assembler. [#45713](https://github.com/woocommerce/woocommerce/pull/45713) +* Fix - CYS - reduce editor instance re-render. [#45458](https://github.com/woocommerce/woocommerce/pull/45458) +* Fix - CYS - Set a default width for the site logo after uploading it. [#45384](https://github.com/woocommerce/woocommerce/pull/45384) +* Fix - CYS: Fix Header/Footer template parts disappear [#45735](https://github.com/woocommerce/woocommerce/pull/45735) +* Fix - CYS: fix the footer large pattern - use only one navigation block [#45308](https://github.com/woocommerce/woocommerce/pull/45308) +* Fix - CYS: fix Undefined array key queryId warning [#45399](https://github.com/woocommerce/woocommerce/pull/45399) +* Fix - Ensure the "Didn’t find a theme you like" text and the "Design your own" banner are displayed exclusively at the bottom of the themes tab on WooCommerce > Extensions. [#45706](https://github.com/woocommerce/woocommerce/pull/45706) +* Fix - Ensure the is_super_admin REST field contains the correct value [#45235](https://github.com/woocommerce/woocommerce/pull/45235) +* Fix - Experimental: Fix: Regression introduced in #44757 that breaks the inspector setting of the new attribute filter block. [#45276](https://github.com/woocommerce/woocommerce/pull/45276) +* Fix - Fix alignment issues in the generated content of the Refunds page [#45292](https://github.com/woocommerce/woocommerce/pull/45292) +* Fix - Fix an issue where shoppers could select invalid price ranges in the Product Filter: Price (Beta) block [#45403](https://github.com/woocommerce/woocommerce/pull/45403) +* Fix - Fix block templates not being rendered in extension taxonomies [#44850](https://github.com/woocommerce/woocommerce/pull/44850) +* Fix - Fix broken CSS styles of the `totalValue` filter. [#45732](https://github.com/woocommerce/woocommerce/pull/45732) +* Fix - Fixes order counts in WooCommerce Status dashboard widget. [#44734](https://github.com/woocommerce/woocommerce/pull/44734) +* Fix - Fix failing e2e customer list test by skipping blank slate [#45261](https://github.com/woocommerce/woocommerce/pull/45261) +* Fix - Fix organization tab e2e tests #45692 [#45692](https://github.com/woocommerce/woocommerce/pull/45692) +* Fix - Fix styling issue for the Price Filter block preventing fields from appearing inline when the Inline input fields option is enabled [#45197](https://github.com/woocommerce/woocommerce/pull/45197) +* Fix - Fix the customer list e2e test for PR merge workflow [#45229](https://github.com/woocommerce/woocommerce/pull/45229) +* Fix - Gracefully handle posts to HPOS redirect when backup post no longer exists. [#45605](https://github.com/woocommerce/woocommerce/pull/45605) +* Fix - Include simple product support in the attributes filter within the analytics orders view. [#44901](https://github.com/woocommerce/woocommerce/pull/44901) +* Fix - Make sure backup posts are restored during sync when HPOS is enabled. [#45332](https://github.com/woocommerce/woocommerce/pull/45332) +* Fix - Normalize Slots on Settings pages by creating scopes for each page that has a Slot [#45152](https://github.com/woocommerce/woocommerce/pull/45152) +* Fix - Prevent fatal error when updating HPOS setting without changing value. [#45604](https://github.com/woocommerce/woocommerce/pull/45604) +* Fix - Prevent possible type error during install routine. [#45730](https://github.com/woocommerce/woocommerce/pull/45730) +* Fix - Prevent user interaction with the Product Filter: Price (Beta) block within the Editor. [#45602](https://github.com/woocommerce/woocommerce/pull/45602) +* Fix - Product Elements: fix some warning thrown when there was no post ID available [#45675](https://github.com/woocommerce/woocommerce/pull/45675) +* Fix - Product results count block update with product collection pagination & filtering. [#45556](https://github.com/woocommerce/woocommerce/pull/45556) +* Fix - Rename ProductTemplate namespace #45594 [#45594](https://github.com/woocommerce/woocommerce/pull/45594) +* Fix - Reset Product Collection block pagination when filters change. [#45693](https://github.com/woocommerce/woocommerce/pull/45693) +* Fix - Tax task - do not require postcode input for countries without postcode. [#45367](https://github.com/woocommerce/woocommerce/pull/45367) +* Fix - Use regular_price to determine if product is not sale and don't rely only on price for product_meta_lookup [#43011](https://github.com/woocommerce/woocommerce/pull/43011) +* Fix - Using ActionScheduler to schedule fetching of in-app marketplace promotions. [#45628](https://github.com/woocommerce/woocommerce/pull/45628) +* Fix - [CYS] Fix the intro path when exiting the assembler. [#44771](https://github.com/woocommerce/woocommerce/pull/44771) +* Fix - [CYS] Improve logic to ensure that the font is active. [#45385](https://github.com/woocommerce/woocommerce/pull/45385) +* Add - Displays a red badge on in-app My Subscriptions tab if Woo.com Update Manager is not installed or activated [#46088](https://github.com/woocommerce/woocommerce/pull/46088) +* Add - '; + +/** + * Start token for matching string in doc file. + * + * @type {string} + */ +const END_TOKEN = ''; + +/** + * Regular expression using tokens for matching in doc file. + * Note: `.` does not match new lines, so [^] is used. + * + * @type {RegExp} + */ +const TOKEN_PATTERN = new RegExp( START_TOKEN + '[^]*' + END_TOKEN ); + +/** + * Returns list of keys, filtering out any experimental + * and wrapping keys with ~~ to strikeout false values. + * + * @type {Object} obj + * @return {string[]} Array of truthy keys + */ +function getTruthyKeys( obj ) { + return Object.keys( obj ) + .filter( ( key ) => ! key.startsWith( '__exp' ) ) + .map( ( key ) => ( obj[ key ] ? key : `~~${ key }~~` ) ); +} + +/** + * Process list of object that may contain inner keys. + * For example: spacing( margin, padding ). + * + * @param {Object} obj + * @return {string[]} Array of keys (inner keys) + */ +function processObjWithInnerKeys( obj ) { + const rtn = []; + + const kvs = getTruthyKeys( obj ); + + kvs.forEach( ( key ) => { + if ( Array.isArray( obj[ key ] ) ) { + rtn.push( `${ key } (${ obj[ key ].sort().join( ', ' ) })` ); + } else if ( typeof obj[ key ] === 'object' ) { + const innerKeys = getTruthyKeys( obj[ key ] ); + rtn.push( `${ key } (${ innerKeys.sort().join( ', ' ) })` ); + } else { + rtn.push( key ); + } + } ); + return rtn; +} + +/** + * Augment supports with additional default props. + * + * The color support if specified defaults background and text, if + * not disabled. So adding { color: 'link' } support also brings along + * background and text. + * + * @param {Object} supports - keys supported by blokc + * @return {Object} supports augmented with defaults + */ +function augmentSupports( supports ) { + if ( supports && 'color' in supports ) { + // If backgroud or text is not specified (true or false) + // then add it as true.a + if ( + typeof supports.color === 'object' && + ! ( 'background' in supports.color ) + ) { + supports.color.background = true; + } + if ( + typeof supports.color === 'object' && + ! ( 'text' in supports.color ) + ) { + supports.color.text = true; + } + } + return supports; +} + +/** + * Reads block.json file and returns markdown formatted entry. + * + * @param {string} filename + * + * @return {string} markdown + */ +function readBlockJSON( filename ) { + const blockjson = require( filename ); + let supportsList = []; + let attributes = []; + + if ( typeof blockjson.name === 'undefined' ) { + return ``; + } + + if ( typeof blockjson.supports !== 'undefined' ) { + const supportsAugmented = augmentSupports( blockjson.supports ); + + if ( supportsAugmented ) { + supportsList = processObjWithInnerKeys( supportsAugmented ); + } + } + + if ( typeof blockjson.attributes !== 'undefined' ) { + attributes = getTruthyKeys( blockjson.attributes ); + } + + return ` +## ${ blockjson.title } - ${ blockjson.name } + +${ blockjson.description || '' } + +- **Name:** ${ blockjson.name } +- **Category:** ${ blockjson.category || '' } +- **Ancestor:** ${ blockjson.ancestor || '' } +- **Parent:** ${ blockjson.parent || '' } +- **Supports:** ${ supportsList || supportsList.sort().join( ', ' ) } +- **Attributes:** ${ attributes || attributes.sort().join( ', ' ) } +`; +} + +function getFiles( dir, filesArray, fileExtension ) { + const files = fs.readdirSync( dir ); + + files.forEach( ( file ) => { + const filePath = path.join( dir, file ); + const fileStat = fs.statSync( filePath ); + + if ( fileStat.isDirectory() ) { + getFiles( filePath, filesArray, fileExtension ); + } else if ( path.extname( file ) === fileExtension ) { + filesArray.push( filePath.replace( /\\/g, '/' ) ); + } + } ); + + return filesArray; +} + +const files = getFiles( BLOCK_LIBRARY_DIR, [], '.json' ); + +let autogen = ''; + +files.forEach( ( file ) => { + const markup = readBlockJSON( file ); + autogen += markup; +} ); + +let docsContent = fs.readFileSync( BLOCK_LIBRARY_DOCS_FILE, { + encoding: 'utf8', + flag: 'r', +} ); + +// Add delimiters back. +autogen = START_TOKEN + '\n' + autogen + '\n' + END_TOKEN; +docsContent = docsContent.replace( TOKEN_PATTERN, autogen ); + +// write back out +fs.writeFileSync( BLOCK_LIBRARY_DOCS_FILE, docsContent, { encoding: 'utf8' } ); diff --git a/plugins/woocommerce-blocks/bin/webpack-entries.js b/plugins/woocommerce-blocks/bin/webpack-entries.js index f3b0543ffe990..fe01fccc5f8cf 100644 --- a/plugins/woocommerce-blocks/bin/webpack-entries.js +++ b/plugins/woocommerce-blocks/bin/webpack-entries.js @@ -25,6 +25,7 @@ const blocks = { cart: {}, 'catalog-sorting': {}, checkout: {}, + 'coming-soon': {}, 'customer-account': {}, 'featured-category': { customDir: 'featured-items/featured-category', @@ -217,6 +218,7 @@ const entries = { wcBlocksSharedContext: './assets/js/shared/context/index.js', wcBlocksSharedHocs: './assets/js/shared/hocs/index.js', priceFormat: './packages/prices/index.js', + wcTypes: './assets/js/types/index.ts', // interactivity components, exported as separate entries for now 'wc-interactivity-dropdown': diff --git a/plugins/woocommerce-blocks/bin/webpack-helpers.js b/plugins/woocommerce-blocks/bin/webpack-helpers.js index f7b7d6de32af9..b09d241944636 100644 --- a/plugins/woocommerce-blocks/bin/webpack-helpers.js +++ b/plugins/woocommerce-blocks/bin/webpack-helpers.js @@ -19,6 +19,7 @@ const wcDepMap = { '@woocommerce/blocks-checkout': [ 'wc', 'blocksCheckout' ], '@woocommerce/blocks-components': [ 'wc', 'blocksComponents' ], '@woocommerce/interactivity': [ 'wc', '__experimentalInteractivity' ], + '@woocommerce/types': [ 'wc', 'wcTypes' ], }; const wcHandleMap = { @@ -32,6 +33,7 @@ const wcHandleMap = { '@woocommerce/blocks-checkout': 'wc-blocks-checkout', '@woocommerce/blocks-components': 'wc-blocks-components', '@woocommerce/interactivity': 'wc-interactivity', + '@woocommerce/types': 'wc-types', }; const getAlias = ( options = {} ) => { @@ -97,6 +99,8 @@ const getAlias = ( options = {} ) => { __dirname, `../assets/js/templates/` ), + 'react/jsx-dev-runtime': require.resolve( 'react/jsx-dev-runtime.js' ), + 'react/jsx-runtime': require.resolve( 'react/jsx-runtime.js' ), }; }; diff --git a/plugins/woocommerce-blocks/docs/README.md b/plugins/woocommerce-blocks/docs/README.md index 1a591945f89b2..19a9a00406c68 100644 --- a/plugins/woocommerce-blocks/docs/README.md +++ b/plugins/woocommerce-blocks/docs/README.md @@ -6,6 +6,7 @@ - [Internal developers](#internal-developers) - [Third-party developers](#third-party-developers) - [Designers](#designers) +- [Block References](#block-references) - [Developer Resources](#developer-resources) - [Tools](#tools) - [Articles](#articles) @@ -119,6 +120,10 @@ The WooCommerce Blocks Handbook provides documentation for designers and develop - [Class names update in 2.8.0](designers/theming/class-names-update-280.md) - [Product grid blocks style update in 2.7.0](designers/theming/product-grid-270.md) +## Block References + +- [Block References](block-references/block-references.md) + ## Developer Resources ### Tools @@ -136,7 +141,7 @@ The following posts from [developer.woo.com](https://developer.woocommerce.com/c ### Tutorials -The following tutorials from [developer.woo.com](https://developer.woocommerce.com/category/tutorials/) help you with extending the WooCommerce Blocks plugin. +The following tutorials from [developer.woo.com](https://developer.woocommerce.com/) help you with extending the WooCommerce Blocks plugin. - [📺 Tutorial: Extending the WooCommerce Checkout Block](https://developer.woocommerce.com/2023/08/07/extending-the-woocommerce-checkout-block-to-add-custom-shipping-options/) - [Hiding Shipping and Payment Options in the Cart and Checkout Blocks](https://developer.woocommerce.com/2022/05/20/hiding-shipping-and-payment-options-in-the-cart-and-checkout-blocks/) diff --git a/plugins/woocommerce-blocks/docs/block-references/block-references.md b/plugins/woocommerce-blocks/docs/block-references/block-references.md new file mode 100644 index 0000000000000..e3f0e63e946ed --- /dev/null +++ b/plugins/woocommerce-blocks/docs/block-references/block-references.md @@ -0,0 +1,1305 @@ +# Woo Blocks Reference + +This page lists the Woo blocks included in the package. (Incomplete as there are still blocks that are not using block.json definition) + + + +## Add to Cart with Options - woocommerce/add-to-cart-form + +Display a button so the customer can add a product to their cart. Options will also be displayed depending on product type. e.g. quantity, variation. + +- **Name:** woocommerce/add-to-cart-form +- **Category:** woocommerce-product-elements +- **Ancestor:** +- **Parent:** +- **Supports:** +- **Attributes:** isDescendentOfSingleProductBlock + +## Product Average Rating (Beta) - woocommerce/product-average-rating + +Display the average rating of a product + +- **Name:** woocommerce/product-average-rating +- **Category:** woocommerce-product-elements +- **Ancestor:** woocommerce/single-product +- **Parent:** +- **Supports:** +- **Attributes:** textAlign + +## Add to Cart Button - woocommerce/product-button + +Display a call to action button which either adds the product to the cart, or links to the product page. + +- **Name:** woocommerce/product-button +- **Category:** woocommerce-product-elements +- **Ancestor:** woocommerce/all-products,woocommerce/single-product,core/post-template,woocommerce/product-template +- **Parent:** +- **Supports:** align (full, wide),color (link, text, ~~background~~),interactivity,~~html~~,typography (fontSize, lineHeight) +- **Attributes:** productId,textAlign,width,isDescendentOfSingleProductBlock,isDescendentOfQueryLoop + +## Product Details - woocommerce/product-details + +Display a product's description, attributes, and reviews. + +- **Name:** woocommerce/product-details +- **Category:** woocommerce-product-elements +- **Ancestor:** +- **Parent:** +- **Supports:** align,spacing (margin) +- **Attributes:** + +## Product Image Gallery - woocommerce/product-image-gallery + +Display a product's images. + +- **Name:** woocommerce/product-image-gallery +- **Category:** woocommerce-product-elements +- **Ancestor:** +- **Parent:** +- **Supports:** align,~~multiple~~ +- **Attributes:** + +## Product Meta - woocommerce/product-meta + +Display a product’s SKU, categories, tags, and more. + +- **Name:** woocommerce/product-meta +- **Category:** woocommerce-product-elements +- **Ancestor:** +- **Parent:** +- **Supports:** align,~~reusable~~ +- **Attributes:** + +## Product Reviews - woocommerce/product-reviews + +A block that shows the reviews for a product. + +- **Name:** woocommerce/product-reviews +- **Category:** woocommerce-product-elements +- **Ancestor:** +- **Parent:** +- **Supports:** +- **Attributes:** + +## Product Rating - woocommerce/product-rating + +Display the average rating of a product. + +- **Name:** woocommerce/product-rating +- **Category:** +- **Ancestor:** +- **Parent:** +- **Supports:** align +- **Attributes:** productId,isDescendentOfQueryLoop,textAlign,isDescendentOfSingleProductBlock,isDescendentOfSingleProductTemplate + +## Product Rating Counter - woocommerce/product-rating-counter + +Display the review count of a product + +- **Name:** woocommerce/product-rating-counter +- **Category:** woocommerce-product-elements +- **Ancestor:** woocommerce/single-product +- **Parent:** +- **Supports:** align +- **Attributes:** productId,isDescendentOfQueryLoop,textAlign,isDescendentOfSingleProductBlock,isDescendentOfSingleProductTemplate + +## Product Rating Stars - woocommerce/product-rating-stars + +Display the average rating of a product with stars + +- **Name:** woocommerce/product-rating-stars +- **Category:** woocommerce-product-elements +- **Ancestor:** woocommerce/single-product +- **Parent:** +- **Supports:** align +- **Attributes:** productId,isDescendentOfQueryLoop,textAlign,isDescendentOfSingleProductBlock,isDescendentOfSingleProductTemplate + +## Related Products - woocommerce/related-products + +Display related products. + +- **Name:** woocommerce/related-products +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align,~~reusable~~ +- **Attributes:** + +## Active Filters Controls - woocommerce/active-filters + +Display the currently active filters. + +- **Name:** woocommerce/active-filters +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~html~~,~~multiple~~,~~inserter~~,color (text, ~~background~~),~~lock~~ +- **Attributes:** displayStyle,headingLevel + +## Filter by Attribute Controls - woocommerce/attribute-filter + +Enable customers to filter the product grid by selecting one or more attributes, such as color. + +- **Name:** woocommerce/attribute-filter +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~html~~,color (text, ~~background~~),~~inserter~~,~~lock~~ +- **Attributes:** className,attributeId,showCounts,queryType,headingLevel,displayStyle,showFilterButton,selectType,isPreview + +## Store Breadcrumbs - woocommerce/breadcrumbs + +Enable customers to keep track of their location within the store and navigate back to parent pages. + +- **Name:** woocommerce/breadcrumbs +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),color (link, text, ~~background~~),~~html~~,typography (fontSize, lineHeight) +- **Attributes:** contentJustification,fontSize,align + +## Accepted Payment Methods - woocommerce/cart-accepted-payment-methods-block + +Display accepted payment methods. + +- **Name:** woocommerce/cart-accepted-payment-methods-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,inserter +- **Attributes:** + +## Cart Cross-Sells - woocommerce/cart-cross-sells-block + +Shows the Cross-Sells block. + +- **Name:** woocommerce/cart-cross-sells-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-items-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,inserter +- **Attributes:** + +## Cart Cross-Sells Products - woocommerce/cart-cross-sells-products-block + +Shows the Cross-Sells products. + +- **Name:** woocommerce/cart-cross-sells-products-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-cross-sells-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** columns,lock + +## Express Checkout - woocommerce/cart-express-payment-block + +Allow customers to breeze through with quick payment options. + +- **Name:** woocommerce/cart-express-payment-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Cart Items - woocommerce/cart-items-block + +Column containing cart items. + +- **Name:** woocommerce/cart-items-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/filled-cart-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Cart Line Items - woocommerce/cart-line-items-block + +Block containing current line items in Cart. + +- **Name:** woocommerce/cart-line-items-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-items-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Order Summary - woocommerce/cart-order-summary-block + +Show customers a summary of their order. + +- **Name:** woocommerce/cart-order-summary-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Coupon Form - woocommerce/cart-order-summary-coupon-form-block + +Shows the apply coupon form. + +- **Name:** woocommerce/cart-order-summary-coupon-form-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-order-summary-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~ +- **Attributes:** className,lock + +## Discount - woocommerce/cart-order-summary-discount-block + +Shows the cart discount row. + +- **Name:** woocommerce/cart-order-summary-discount-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Fees - woocommerce/cart-order-summary-fee-block + +Shows the cart fee row. + +- **Name:** woocommerce/cart-order-summary-fee-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Heading - woocommerce/cart-order-summary-heading-block + +Shows the heading row. + +- **Name:** woocommerce/cart-order-summary-heading-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-order-summary-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~ +- **Attributes:** className,content,lock + +## Shipping - woocommerce/cart-order-summary-shipping-block + +Shows the cart shipping row. + +- **Name:** woocommerce/cart-order-summary-shipping-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** lock + +## Subtotal - woocommerce/cart-order-summary-subtotal-block + +Shows the cart subtotal row. + +- **Name:** woocommerce/cart-order-summary-subtotal-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Taxes - woocommerce/cart-order-summary-taxes-block + +Shows the cart taxes row. + +- **Name:** woocommerce/cart-order-summary-taxes-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Totals - woocommerce/cart-order-summary-totals-block + +Shows the subtotal, fees, discounts, shipping and taxes. + +- **Name:** woocommerce/cart-order-summary-totals-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-order-summary-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Cart Totals - woocommerce/cart-totals-block + +Column containing the cart totals. + +- **Name:** woocommerce/cart-totals-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/filled-cart-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** checkbox,text,lock + +## Empty Cart - woocommerce/empty-cart-block + +Contains blocks that are displayed when the cart is empty. + +- **Name:** woocommerce/empty-cart-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart +- **Supports:** align (wide),~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Filled Cart - woocommerce/filled-cart-block + +Contains blocks that are displayed when the cart contains products. + +- **Name:** woocommerce/filled-cart-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart +- **Supports:** align (wide),~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Proceed to Checkout - woocommerce/proceed-to-checkout-block + +Allow customers proceed to Checkout. + +- **Name:** woocommerce/proceed-to-checkout-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/cart-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Catalog Sorting - woocommerce/catalog-sorting + +Enable customers to change the sorting order of the products. + +- **Name:** woocommerce/catalog-sorting +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** color (text, ~~background~~),typography (fontSize) +- **Attributes:** fontSize + +## Checkout - woocommerce/checkout + +Display a checkout form so your customers can submit orders. + +- **Name:** woocommerce/checkout +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (wide),~~html~~,~~multiple~~ +- **Attributes:** isPreview,showCompanyField,requireCompanyField,showApartmentField,showPhoneField,requirePhoneField,align + +## Actions - woocommerce/checkout-actions-block + +Allow customers to place their order. + +- **Name:** woocommerce/checkout-actions-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Additional information - woocommerce/checkout-additional-information-block + +Render additional fields in the 'Additional information' location. + +- **Name:** woocommerce/checkout-additional-information-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~ +- **Attributes:** className,lock + +## Billing Address - woocommerce/checkout-billing-address-block + +Collect your customer's billing address. + +- **Name:** woocommerce/checkout-billing-address-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Contact Information - woocommerce/checkout-contact-information-block + +Collect your customer's contact information. + +- **Name:** woocommerce/checkout-contact-information-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Express Checkout - woocommerce/checkout-express-payment-block + +Allow customers to breeze through with quick payment options. + +- **Name:** woocommerce/checkout-express-payment-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** className,lock + +## Checkout Fields - woocommerce/checkout-fields-block + +Column containing checkout address fields. + +- **Name:** woocommerce/checkout-fields-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** className,lock + +## Order Note - woocommerce/checkout-order-note-block + +Allow customers to add a note to their order. + +- **Name:** woocommerce/checkout-order-note-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~ +- **Attributes:** className,lock + +## Order Summary - woocommerce/checkout-order-summary-block + +Show customers a summary of their order. + +- **Name:** woocommerce/checkout-order-summary-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Cart Items - woocommerce/checkout-order-summary-cart-items-block + +Shows cart items. + +- **Name:** woocommerce/checkout-order-summary-cart-items-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-order-summary-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Coupon Form - woocommerce/checkout-order-summary-coupon-form-block + +Shows the apply coupon form. + +- **Name:** woocommerce/checkout-order-summary-coupon-form-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-order-summary-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~ +- **Attributes:** className,lock + +## Discount - woocommerce/checkout-order-summary-discount-block + +Shows the cart discount row. + +- **Name:** woocommerce/checkout-order-summary-discount-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Fees - woocommerce/checkout-order-summary-fee-block + +Shows the cart fee row. + +- **Name:** woocommerce/checkout-order-summary-fee-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Shipping - woocommerce/checkout-order-summary-shipping-block + +Shows the cart shipping row. + +- **Name:** woocommerce/checkout-order-summary-shipping-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** lock + +## Subtotal - woocommerce/checkout-order-summary-subtotal-block + +Shows the cart subtotal row. + +- **Name:** woocommerce/checkout-order-summary-subtotal-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Taxes - woocommerce/checkout-order-summary-taxes-block + +Shows the cart taxes row. + +- **Name:** woocommerce/checkout-order-summary-taxes-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-order-summary-totals-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Totals - woocommerce/checkout-order-summary-totals-block + +Shows the subtotal, fees, discounts, shipping and taxes. + +- **Name:** woocommerce/checkout-order-summary-totals-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-order-summary-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~lock~~ +- **Attributes:** className,lock + +## Payment Options - woocommerce/checkout-payment-block + +Payment options for your store. + +- **Name:** woocommerce/checkout-payment-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Pickup Method - woocommerce/checkout-pickup-options-block + +Shows local pickup options. + +- **Name:** woocommerce/checkout-pickup-options-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Shipping Address - woocommerce/checkout-shipping-address-block + +Collect your customer's shipping address. + +- **Name:** woocommerce/checkout-shipping-address-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Shipping Method - woocommerce/checkout-shipping-method-block + +Select between shipping or local pickup. + +- **Name:** woocommerce/checkout-shipping-method-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Shipping Options - woocommerce/checkout-shipping-methods-block + +Display shipping options and rates for your store. + +- **Name:** woocommerce/checkout-shipping-methods-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Terms and Conditions - woocommerce/checkout-terms-block + +Ensure that customers agree to your Terms & Conditions and Privacy Policy. + +- **Name:** woocommerce/checkout-terms-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout-fields-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~ +- **Attributes:** className,checkbox,text + +## Checkout Totals - woocommerce/checkout-totals-block + +Column containing the checkout totals. + +- **Name:** woocommerce/checkout-totals-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/checkout +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** className,checkbox,text + +## Classic Shortcode - woocommerce/classic-shortcode + +Renders classic WooCommerce shortcodes. + +- **Name:** woocommerce/classic-shortcode +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align,~~html~~,~~multiple~~,~~reusable~~,inserter +- **Attributes:** shortcode,align + +## Customer account - woocommerce/customer-account + +A block that allows your customers to log in and out of their accounts in your store. + +- **Name:** woocommerce/customer-account +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align,color (background, text),typography (fontSize),spacing (margin, padding) +- **Attributes:** displayStyle,iconStyle,iconClass + +## Featured Category - woocommerce/featured-category + +Visually highlight a product category and encourage prompt action. + +- **Name:** woocommerce/featured-category +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~,color (background, text),spacing (padding) +- **Attributes:** alt,contentAlign,dimRatio,editMode,focalPoint,imageFit,hasParallax,isRepeated,mediaId,mediaSrc,minHeight,linkText,categoryId,overlayColor,overlayGradient,previewCategory,showDesc + +## Featured Product - woocommerce/featured-product + +Highlight a product or variation. + +- **Name:** woocommerce/featured-product +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~,color (background, text),spacing (padding),multiple +- **Attributes:** alt,contentAlign,dimRatio,editMode,focalPoint,imageFit,hasParallax,isRepeated,mediaId,mediaSrc,minHeight,linkText,overlayColor,overlayGradient,productId,previewProduct,showDesc,showPrice + +## Filter Block - woocommerce/filter-wrapper + + + +- **Name:** woocommerce/filter-wrapper +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** +- **Attributes:** filterType,heading + +## Hand-picked Products - woocommerce/handpicked-products + +Display a selection of hand-picked products in a grid. + +- **Name:** woocommerce/handpicked-products +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~ +- **Attributes:** align,columns,contentVisibility,orderby,products,alignButtons,isPreview + +## Mini-Cart - woocommerce/mini-cart + +Display a button for shoppers to quickly view their cart. + +- **Name:** woocommerce/mini-cart +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~html~~,~~multiple~~,typography (fontSize) +- **Attributes:** isPreview,miniCartIcon,addToCartBehaviour,hasHiddenPrice,cartAndCheckoutRenderStyle,priceColor,priceColorValue,iconColor,iconColorValue,productCountColor,productCountColorValue + +## Empty Mini-Cart view - woocommerce/empty-mini-cart-contents-block + +Blocks that are displayed when the Mini-Cart is empty. + +- **Name:** woocommerce/empty-mini-cart-contents-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/mini-cart-contents +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Filled Mini-Cart view - woocommerce/filled-mini-cart-contents-block + +Contains blocks that display the content of the Mini-Cart. + +- **Name:** woocommerce/filled-mini-cart-contents-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/mini-cart-contents +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Mini-Cart View Cart Button - woocommerce/mini-cart-cart-button-block + +Block that displays the cart button when the Mini-Cart has products. + +- **Name:** woocommerce/mini-cart-cart-button-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/mini-cart-footer-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,inserter,color (background, text) +- **Attributes:** lock + +## Mini-Cart Proceed to Checkout Button - woocommerce/mini-cart-checkout-button-block + +Block that displays the checkout button when the Mini-Cart has products. + +- **Name:** woocommerce/mini-cart-checkout-button-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/mini-cart-footer-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,inserter,color (background, text) +- **Attributes:** lock + +## Mini-Cart Footer - woocommerce/mini-cart-footer-block + +Block that displays the footer of the Mini-Cart block. + +- **Name:** woocommerce/mini-cart-footer-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/filled-mini-cart-contents-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Mini-Cart Items - woocommerce/mini-cart-items-block + +Contains the products table and other custom blocks of filled mini-cart. + +- **Name:** woocommerce/mini-cart-items-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/filled-mini-cart-contents-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Mini-Cart Products Table - woocommerce/mini-cart-products-table-block + +Block that displays the products table of the Mini-Cart block. + +- **Name:** woocommerce/mini-cart-products-table-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/mini-cart-items-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~ +- **Attributes:** lock + +## Mini-Cart Shopping Button - woocommerce/mini-cart-shopping-button-block + +Block that displays the shopping button when the Mini-Cart is empty. + +- **Name:** woocommerce/mini-cart-shopping-button-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/empty-mini-cart-contents-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,inserter,color (background, text) +- **Attributes:** lock + +## Mini-Cart Title - woocommerce/mini-cart-title-block + +Block that displays the title of the Mini-Cart block. + +- **Name:** woocommerce/mini-cart-title-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/filled-mini-cart-contents-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~,color (text, ~~background~~),typography (fontSize) +- **Attributes:** lock + +## Mini-Cart Title Items Counter - woocommerce/mini-cart-title-items-counter-block + +Block that displays the items counter part of the Mini-Cart Title block. + +- **Name:** woocommerce/mini-cart-title-items-counter-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/mini-cart-title-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~,color (background, text),typography (fontSize),spacing (padding) +- **Attributes:** + +## Mini-Cart Title Label - woocommerce/mini-cart-title-label-block + +Block that displays the 'Your cart' part of the Mini-Cart Title block. + +- **Name:** woocommerce/mini-cart-title-label-block +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** woocommerce/mini-cart-title-block +- **Supports:** ~~align~~,~~html~~,~~multiple~~,~~reusable~~,~~inserter~~,~~lock~~,color (background, text),typography (fontSize),spacing (padding) +- **Attributes:** label + +## Additional Field List - woocommerce/order-confirmation-additional-fields + +Display the list of additional field values from the current order. + +- **Name:** woocommerce/order-confirmation-additional-fields +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,spacing (margin, padding) +- **Attributes:** align,className + +## Additional Fields - woocommerce/order-confirmation-additional-fields-wrapper + +Display additional checkout fields from the 'contact' and 'additional' locations. + +- **Name:** woocommerce/order-confirmation-additional-fields-wrapper +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,spacing (margin, padding) +- **Attributes:** heading + +## Additional Information - woocommerce/order-confirmation-additional-information + +Displays additional information provided by third-party extensions for the current order. + +- **Name:** woocommerce/order-confirmation-additional-information +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,spacing (margin, padding) +- **Attributes:** align,className + +## Billing Address - woocommerce/order-confirmation-billing-address + +Display the order confirmation billing address. + +- **Name:** woocommerce/order-confirmation-billing-address +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~inserter~~,~~html~~,color (background, text),typography (fontSize, lineHeight),spacing (margin, padding) +- **Attributes:** align + +## Billing Address Section - woocommerce/order-confirmation-billing-wrapper + +Display the order confirmation billing section. + +- **Name:** woocommerce/order-confirmation-billing-wrapper +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,spacing (margin, padding) +- **Attributes:** heading + +## Order Downloads - woocommerce/order-confirmation-downloads + +Display links to purchased downloads. + +- **Name:** woocommerce/order-confirmation-downloads +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,typography (fontSize, lineHeight),color (background, gradients, link, text),spacing (margin, padding) +- **Attributes:** align,className + +## Downloads Section - woocommerce/order-confirmation-downloads-wrapper + +Display the downloadable products section. + +- **Name:** woocommerce/order-confirmation-downloads-wrapper +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,spacing (margin, padding) +- **Attributes:** heading + +## Shipping Address - woocommerce/order-confirmation-shipping-address + +Display the order confirmation shipping address. + +- **Name:** woocommerce/order-confirmation-shipping-address +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~inserter~~,~~html~~,color (background, text),typography (fontSize, lineHeight),spacing (margin, padding) +- **Attributes:** align + +## Shipping Address Section - woocommerce/order-confirmation-shipping-wrapper + +Display the order confirmation shipping section. + +- **Name:** woocommerce/order-confirmation-shipping-wrapper +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,spacing (margin, padding) +- **Attributes:** heading + +## Order Status - woocommerce/order-confirmation-status + +Display a "thank you" message, or a sentence regarding the current order status. + +- **Name:** woocommerce/order-confirmation-status +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,typography (fontSize, lineHeight),color (background, gradients, text),spacing (margin, padding) +- **Attributes:** align,className + +## Order Summary - woocommerce/order-confirmation-summary + +Display the order summary on the order confirmation page. + +- **Name:** woocommerce/order-confirmation-summary +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,typography (fontSize, lineHeight),color (background, gradients, text),spacing (margin, padding) +- **Attributes:** align,className + +## Order Totals - woocommerce/order-confirmation-totals + +Display the items purchased and order totals. + +- **Name:** woocommerce/order-confirmation-totals +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,typography (fontSize, lineHeight),color (background, gradients, link, text),spacing (margin, padding) +- **Attributes:** align,className + +## Order Totals Section - woocommerce/order-confirmation-totals-wrapper + +Display the order details section. + +- **Name:** woocommerce/order-confirmation-totals-wrapper +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide),~~html~~,spacing (margin, padding) +- **Attributes:** heading + +## WooCommerce Page - woocommerce/page-content-wrapper + +Displays WooCommerce page content. + +- **Name:** woocommerce/page-content-wrapper +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~html~~,~~multiple~~,~~inserter~~ +- **Attributes:** page + +## Filter by Price Controls - woocommerce/price-filter + +Enable customers to filter the product grid by choosing a price range. + +- **Name:** woocommerce/price-filter +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~html~~,~~multiple~~,color (text, ~~background~~),~~inserter~~,~~lock~~ +- **Attributes:** className,showInputFields,inlineInput,showFilterButton,headingLevel + +## Best Selling Products - woocommerce/product-best-sellers + +Display a grid of your all-time best selling products. + +- **Name:** woocommerce/product-best-sellers +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~ +- **Attributes:** columns,rows,alignButtons,contentVisibility,categories,catOperator,isPreview,stockStatus,editMode,orderby + +## Product Categories List - woocommerce/product-categories + +Show all product categories as a list or dropdown. + +- **Name:** woocommerce/product-categories +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~,color (link, text, ~~background~~),typography (fontSize, lineHeight) +- **Attributes:** align,hasCount,hasImage,hasEmpty,isDropdown,isHierarchical,showChildrenOnly + +## Products by Category - woocommerce/product-category + +Display a grid of products from your selected categories. + +- **Name:** woocommerce/product-category +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~ +- **Attributes:** columns,rows,alignButtons,contentVisibility,categories,catOperator,isPreview,stockStatus,editMode,orderby + +## Product Collection (Beta) - woocommerce/product-collection + +Display a collection of products from your store. + +- **Name:** woocommerce/product-collection +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),anchor,~~html~~,interactivity +- **Attributes:** queryId,query,tagName,displayLayout,convertedFromProducts,collection,hideControls,queryContextIncludes,forcePageReload + +## No results - woocommerce/product-collection-no-results + +The contents of this block will display when there are no products found. + +- **Name:** woocommerce/product-collection-no-results +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-collection +- **Parent:** +- **Supports:** align,~~reusable~~,~~html~~,color (background, gradients, link, text),typography (fontSize, lineHeight) +- **Attributes:** + +## Product Filter - woocommerce/product-filter + +A block that adds product filters to the product collection. + +- **Name:** woocommerce/product-filter +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~html~~,~~reusable~~ +- **Attributes:** filterType,heading,isPreview + +## Product Filter: Active Filters (Beta) - woocommerce/product-filter-active + +Display the currently active filters. + +- **Name:** woocommerce/product-filter-active +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filter +- **Parent:** +- **Supports:** interactivity,~~inserter~~,color (text, ~~background~~) +- **Attributes:** displayStyle + +## Product Filter: Attribute (Beta) - woocommerce/product-filter-attribute + +Enable customers to filter the product grid by selecting one or more attributes, such as color. + +- **Name:** woocommerce/product-filter-attribute +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filter +- **Parent:** +- **Supports:** interactivity,~~inserter~~,color (text, ~~background~~) +- **Attributes:** attributeId,showCounts,queryType,displayStyle,selectType,isPreview + +## Product Filter: Price (Beta) - woocommerce/product-filter-price + +Enable customers to filter the product collection by choosing a price range. + +- **Name:** woocommerce/product-filter-price +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filter +- **Parent:** +- **Supports:** interactivity,~~inserter~~ +- **Attributes:** showInputFields,inlineInput + +## Product Filter: Rating (Beta) - woocommerce/product-filter-rating + +Enable customers to filter the product collection by rating. + +- **Name:** woocommerce/product-filter-rating +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filter +- **Parent:** +- **Supports:** interactivity,~~inserter~~,color (text, ~~background~~) +- **Attributes:** className,showCounts,displayStyle,selectType,isPreview + +## Product Filter: Stock Status (Beta) - woocommerce/product-filter-stock-status + +Enable customers to filter the product collection by stock status. + +- **Name:** woocommerce/product-filter-stock-status +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-filter +- **Parent:** +- **Supports:** interactivity,~~html~~,~~multiple~~,~~inserter~~,color (text, ~~background~~) +- **Attributes:** className,showCounts,displayStyle,selectType,isPreview + +## Product Gallery (Beta) - woocommerce/product-gallery + +Showcase your products relevant images and media. + +- **Name:** woocommerce/product-gallery +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align,~~multiple~~,interactivity +- **Attributes:** thumbnailsPosition,thumbnailsNumberOfThumbnails,pagerDisplayMode,productGalleryClientId,cropImages,hoverZoom,fullScreenOnClick,nextPreviousButtonsPosition,mode + +## Large Image - woocommerce/product-gallery-large-image + +Display the Large Image of a product. + +- **Name:** woocommerce/product-gallery-large-image +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-gallery +- **Parent:** +- **Supports:** interactivity +- **Attributes:** + +## Next/Previous Buttons - woocommerce/product-gallery-large-image-next-previous + +Display next and previous buttons. + +- **Name:** woocommerce/product-gallery-large-image-next-previous +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-gallery-large-image +- **Parent:** +- **Supports:** layout (allowVerticalAlignment, default, ~~allowInheriting~~, ~~allowJustification~~, ~~allowOrientation~~) +- **Attributes:** + +## Pager - woocommerce/product-gallery-pager + +Display the gallery pager. + +- **Name:** woocommerce/product-gallery-pager +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-gallery +- **Parent:** +- **Supports:** +- **Attributes:** + +## Thumbnails - woocommerce/product-gallery-thumbnails + +Display the Thumbnails of a product. + +- **Name:** woocommerce/product-gallery-thumbnails +- **Category:** woocommerce +- **Ancestor:** woocommerce/product-gallery +- **Parent:** +- **Supports:** spacing (margin) +- **Attributes:** + +## Newest Products - woocommerce/product-new + +Display a grid of your newest products. + +- **Name:** woocommerce/product-new +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~ +- **Attributes:** columns,rows,alignButtons,contentVisibility,categories,catOperator,isPreview,stockStatus,editMode,orderby + +## Product Results Count - woocommerce/product-results-count + +Display the number of products on the archive page or search result page. + +- **Name:** woocommerce/product-results-count +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** color (text, ~~background~~),typography (fontSize) +- **Attributes:** + +## Products by Tag - woocommerce/product-tag + +Display a grid of products with selected tags. + +- **Name:** woocommerce/product-tag +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~ +- **Attributes:** columns,rows,alignButtons,contentVisibility,tags,tagOperator,orderby,isPreview,stockStatus + +## Product Template - woocommerce/product-template + +Contains the block elements used to render a product. + +- **Name:** woocommerce/product-template +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~inserter~~,~~reusable~~,~~html~~,align (full, wide),anchor,color (background, gradients, link, text),typography (fontSize, lineHeight) +- **Attributes:** + +## Top Rated Products - woocommerce/product-top-rated + +Display a grid of your top rated products. + +- **Name:** woocommerce/product-top-rated +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~ +- **Attributes:** columns,rows,alignButtons,contentVisibility,categories,catOperator,isPreview,stockStatus,editMode,orderby + +## All Products - woocommerce/all-products + +Display products from your store in a grid layout. + +- **Name:** woocommerce/all-products +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~,~~multiple~~ +- **Attributes:** columns,rows,alignButtons,contentVisibility,orderby,layoutConfig,isPreview + +## Products by Attribute - woocommerce/products-by-attribute + +Display a grid of products with selected attributes. + +- **Name:** woocommerce/products-by-attribute +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide),~~html~~ +- **Attributes:** attributes,attrOperator,columns,contentVisibility,orderby,rows,alignButtons,isPreview,stockStatus + +## Filter by Rating Controls - woocommerce/rating-filter + +Enable customers to filter the product grid by rating. + +- **Name:** woocommerce/rating-filter +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~html~~,~~multiple~~,color,~~inserter~~,~~lock~~ +- **Attributes:** className,showCounts,displayStyle,showFilterButton,selectType,isPreview + +## Single Product - woocommerce/single-product + +Display a single product. + +- **Name:** woocommerce/single-product +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** align (full, wide) +- **Attributes:** isPreview,productId + +## Filter by Stock Controls - woocommerce/stock-filter + +Enable customers to filter the product grid by stock status. + +- **Name:** woocommerce/stock-filter +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~html~~,~~multiple~~,color,~~inserter~~,~~lock~~ +- **Attributes:** className,headingLevel,showCounts,showFilterButton,displayStyle,selectType,isPreview + +## Store Notices - woocommerce/store-notices + +Display shopper-facing notifications generated by WooCommerce or extensions. + +- **Name:** woocommerce/store-notices +- **Category:** woocommerce +- **Ancestor:** +- **Parent:** +- **Supports:** ~~multiple~~,align (full, wide) +- **Attributes:** align + + diff --git a/plugins/woocommerce-blocks/docs/internal-developers/block-client-apis/checkout/checkout-api.md b/plugins/woocommerce-blocks/docs/internal-developers/block-client-apis/checkout/checkout-api.md index 9d9aa230847c0..f1f3add98c675 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/block-client-apis/checkout/checkout-api.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/block-client-apis/checkout/checkout-api.md @@ -39,7 +39,7 @@ Data can be accessed through the following selectors: - `isIdle()`: When the checkout status is `IDLE` this flag is true. Checkout will be this status after any change to checkout state after the block is loaded. It will also be this status when retrying a purchase is possible after processing happens with an error. - `isBeforeProcessing()`: When the checkout status is `BEFORE_PROCESSING` this flag is true. Checkout will be this status when the user submits checkout for processing. - `isProcessing()`: When the checkout status is `PROCESSING` this flag is true. Checkout will be this status when all the observers on the event emitted with the `BEFORE_PROCESSING` status are completed without error. It is during this status that the block will be sending a request to the server on the checkout endpoint for processing the order. -- `isAfterProcessing()`: When the checkout status is `AFTER_PROCESSING` this flag is true. Checkout will have this status after the the block receives the response from the server side processing request. +- `isAfterProcessing()`: When the checkout status is `AFTER_PROCESSING` this flag is true. Checkout will have this status after the block receives the response from the server side processing request. - `isComplete()`: When the checkout status is `COMPLETE` this flag is true. Checkout will have this status after all observers on the events emitted during the `AFTER_PROCESSING` status are completed successfully. When checkout is at this status, the shopper's browser will be redirected to the value of `redirectUrl` at that point (usually the `order-received` route). - `isCalculating()`: This is true when the total is being re-calculated for the order. There are numerous things that might trigger a recalculation of the total: coupons being added or removed, shipping rates updated, shipping rate selected etc. This flag consolidates all activity that might be occurring (including requests to the server that potentially affect calculation of totals). So instead of having to check each of those individual states you can reliably just check if this boolean is true (calculating) or false (not calculating). - `hasOrder()`: This is true when orderId is truthy. diff --git a/plugins/woocommerce-blocks/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md b/plugins/woocommerce-blocks/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md index 757a64425d26c..8f34efce4aa45 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md @@ -77,7 +77,7 @@ The following boolean flags available related to status are: **isProcessing**: When the checkout status is `PROCESSING` this flag is true. Checkout will be this status when all the observers on the event emitted with the `BEFORE_PROCESSING` status are completed without error. It is during this status that the block will be sending a request to the server on the checkout endpoint for processing the order. **Note:** there are some checkout payment status changes that happen during this state as well (outlined in the `PaymentProvider` exposed statuses section). -**isAfterProcessing**: When the checkout status is `AFTER_PROCESSING` this flag is true. Checkout will have this status after the the block receives the response from the server side processing request. +**isAfterProcessing**: When the checkout status is `AFTER_PROCESSING` this flag is true. Checkout will have this status after the block receives the response from the server side processing request. **isComplete**: When the checkout status is `COMPLETE` this flag is true. Checkout will have this status after all observers on the events emitted during the `AFTER_PROCESSING` status are completed successfully. When checkout is at this status, the shopper's browser will be redirected to the value of `redirectUrl` at that point (usually the `order-received` route). diff --git a/plugins/woocommerce-blocks/docs/internal-developers/templates/README.md b/plugins/woocommerce-blocks/docs/internal-developers/templates/README.md index 958790c8010a6..1a1b98a7e9e25 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/templates/README.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/templates/README.md @@ -21,7 +21,7 @@ You can read more about the Full Site Editing (FSE) experience [here](https://de ### Requirements | Software | Minimum Version | -| ----------- | -------------------------------------------------------------------------------------------------------------------------------- | +|-------------|----------------------------------------------------------------------------------------------------------------------------------| | WordPress | 5.9 | | WooCommerce | 6.0 | | Theme | Any [block theme](https://developer.wordpress.org/block-editor/how-to-guides/themes/block-theme-overview/#what-is-a-block-theme) | @@ -40,17 +40,18 @@ The BlockTemplateController.php is primarily responsible for hooking into both W ### Some things to be aware of -- Individual templates are represented by a placeholder block within the Site Editor until we can 'block-ify' these. This is the long-term goal. Until then users will be able to customize templates by adding blocks above and below the placeholder. +- For each template, we have a version represented by a placeholder block and a blockified one, which uses more granular blocks. That was done to keep backwards compatibility for extensions in existing stores. - At the beginning of the project, we unintentionally used the incorrect WooCommerce plugin slug. This has resulted in us maintaining both the incorrect and correct slugs. We reference these via `BlockTemplateUtils::DEPRECATED_PLUGIN_SLUG` and `BlockTemplateUtils::PLUGIN_SLUG`. More information on that [here](https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5423). -- If a theme has a `archive-product.html` template file, but does not have any taxonomy related files. We will automatically duplicate their themes `archive-product.html` file in place of these taxonomy files within `BlockTemplateController->get_block_file_template()`. -- In `BlockTemplateController->get_block_templates()` we filter out templates in production that are behind a feature flag. These will be available in development, but will be absent in production unless the feature flag is removed. +- If a theme has a `archive-product.html` template file, but does not have any taxonomy related files. The `archive-product.html` template will be applied to all product taxonomy archives. Themes can override product taxonomy archive templates with `taxonomy-product_cat.html` and `taxonomy-product_tag.html` templates. ## Related files -| File | Description | Source | Docs | -| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | -| templates/templates/\* | Location in the filesystem where WooCommerce block template HTML files are stored. | [Source files](https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/trunk/templates/templates) | | -| classic-template/\* | The JavaScript block rendered in the Site Editor. This is a server-side rendered component which is handled by ClassicTemplate.php | [Source file](https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/trunk/assets/js/blocks/classic-template) | [README](../../../assets/js/blocks/classic-template/README.md) | -| ClassicTemplate.php | Class used to setup the block on the server-side and render the correct template | [Source file](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/src/BlockTypes/ClassicTemplate.php) | [README](./classic-template.md) | -| BlockTemplateController.php | Class which contains all the business logic which loads the templates into the Site Editor or on the front-end through various hooks available in WordPress & WooCommerce core. | [Source file](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/src/BlockTemplatesController.php) | [README](./block-template-controller.md) | -| BlockTemplateUtils.php | Class containing a collection of useful utility methods. | [Source file](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/src/Utils/BlockTemplateUtils.php) | | +| File | Description | Source | Docs | +|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------| +| templates/templates/\* | Location in the filesystem where WooCommerce block template HTML files are stored. | [Source files](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/templates/templates) | | +| classic-template/\* | The JavaScript block rendered in the Site Editor. This is a server-side rendered component which is handled by ClassicTemplate.php | [Source file](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce-blocks/assets/js/blocks/classic-template) | [README](../../../assets/js/blocks/classic-template/README.md) | +| ClassicTemplate.php | Class used to setup the block on the server-side and render the correct template | [Source file](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Blocks/BlockTypes/ClassicTemplate.php) | [README](./classic-template.md) | +| BlockTemplateController.php | Class which contains all the business logic which loads the templates into the Site Editor or on the front-end through various hooks available in WordPress & WooCommerce core. | [Source file](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Blocks/BlockTemplatesController.php) | [README](./block-template-controller.md) | +| BlockTemplateUtils.php | Class containing a collection of useful utility methods. | [Source file](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Blocks/Utils/BlockTemplateUtils.php) | | +| BlockTemplatesRegistry.php | Class used as a registry of WooCommerce templates. | [Source file](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Blocks/BlockTemplatesRegistry.php) | | +| Individual template classes | Individual classes for each WooCommerce template. | [Source files](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/src/Blocks/Templates) | [README](./individual-template-classes.md) | diff --git a/plugins/woocommerce-blocks/docs/internal-developers/templates/block-template-controller.md b/plugins/woocommerce-blocks/docs/internal-developers/templates/block-template-controller.md index 099c6754d6360..1f1686d1c71ec 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/templates/block-template-controller.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/templates/block-template-controller.md @@ -4,11 +4,7 @@ - [Overview](#overview) - [add_block_templates( $query_result, $query, \$template_type )](#add_block_templates-query_result-query-template_type-) - - [Return value](#return-value) - [get_block_file_template( $template, $id, \$template_type )](#get_block_file_template-template-id-template_type-) - - [Return value](#return-value-1) -- [render_block_template()](#render_block_template) - - [Return value](#return-value-2) The `BlockTemplateController` class contains all the business logic which loads the templates into the Site Editor or on the front-end through various hooks available in WordPress & WooCommerce core. Without documenting every method individually, I will look to provide some insight into key functionality. @@ -39,7 +35,7 @@ This method is applied to the filter `get_block_templates`, which is executed be - In the event the theme has a `archive-product.html` template file, but not category/tag/attribute template files, it is eligible to use the `archive-product.html` file in their place. So we trick Gutenberg in thinking some templates (e.g. category/tag/attribute) have a theme file available if it is using the `archive-product.html` template, even though _technically_ the theme does not have a specific file for them. - Ensuring we do not add irrelevant WooCommerce templates in the returned list. For example, if `$query['post_type']` has a value (e.g. `product`) this means the query is requesting templates related to that specific post type, so we filter out any irrelevant ones. This _could_ be used to show/hide templates from the template dropdown on the "Edit Product" screen in WP Admin. -### Return value +**Return value:** This method will return an array of `WP_Block_Template` values @@ -62,25 +58,6 @@ During step 2 it's important we hook into the `pre_get_block_file_template`. If - Loading the template files from the filesystem, and building a `WP_Block_Template` version of it. -### Return value +**Return value:** This method will return `WP_Block_Template` or `null`. - -## render_block_template() - -This method is applied to the filter `template_redirect` and executed before WordPress determines which template to load. - -This allows us to hook into WooCommerce core through the filter `woocommerce_has_block_template` where we can determine if a specific block template exists and should be loaded. - -**Typically executed when:** - -- A user loads a page on the front-end. - -**This method is responsible for:** - -- Determining if the current page has an appropriate WooCommerce block template available to render. -- Checking if the currently loaded page is from WooCommerce. It then checks if the theme has an appropriate template to use: if it does not, then it finally checks if WooCommerce has a default block template available. If so, we override the value through `woocommerce_has_block_template` to resolve `true`. - -### Return value - -Void. This method does not return a value but rather sets up hooks to render block templates on the front-end. diff --git a/plugins/woocommerce-blocks/docs/internal-developers/templates/individual-template-classes.md b/plugins/woocommerce-blocks/docs/internal-developers/templates/individual-template-classes.md new file mode 100644 index 0000000000000..e14dce714c39d --- /dev/null +++ b/plugins/woocommerce-blocks/docs/internal-developers/templates/individual-template-classes.md @@ -0,0 +1,30 @@ +# Individual template classes + +Each WooCommerce template has its own individual PHP class, which are all registered in BlockTemplatesRegistry. + +## Overview + +These classes have several purposes: + +* define the template metadata like title and description +* hook into `template_redirect` to decide when to render the template (more on this below) +* any other hook or logic specific to that template + +## render_block_template() + +This method is applied to the filter `template_redirect` and executed before WordPress determines which template to load. + +This allows us to hook into WooCommerce core through the filter `woocommerce_has_block_template` where we can determine if the block template should be loaded. + +**Typically executed when:** + +* A user loads a page on the front-end. + +**This method is responsible for:** + +* Determining if the block template has to be rendered in the current page. If so, we override the value through `woocommerce_has_block_template` to resolve `true`. +* Determining if the page that will be rendered contains a Legacy Template block, in which case we disable the compatibility layer through the `woocommerce_disable_compatibility_layer` filter. + +**Return value:** + +Void. This method does not return a value but rather sets up hooks to render block templates on the front-end. diff --git a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/1000.md b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/1000.md index d8f35609ac799..e23429382c4c5 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/1000.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/1000.md @@ -206,7 +206,7 @@ These test cases show how to trigger the new notices during certain activities. 2. Go to `WP Admin » Appearance » Editor`. 3. Select a template (e.g. Templates > Single Product) then click the blue `Edit` button and then click the `Styles` icon in the upper-right corner. Screenshot 2023-02-21 at 13 23 00 -4. Click on the the `Open Style Book` icon (the one that looks like an eye) +4. Click on the `Open Style Book` icon (the one that looks like an eye) Screenshot 2023-02-21 at 13 23 24 5. Verify that both the Cart and the Checkout blocks are visible. diff --git a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/260.md b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/260.md index 3fe633a907e4b..22c6249ebcf32 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/260.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/260.md @@ -36,7 +36,7 @@ These blocks are used in tandem with the All Products block to provide filtering These blocks can be tested by adding them to the same page as the All Products block as the selected values in these blocks affects the products displayed by the All Products block. -- [ ] General testing involves setting up the filter blocks and verifying the the various configurations for the blocks work as expected in the editor (for settings) and the frontend (according to how the block was configured). Validate that when various filters are applied the expected results are shown in the All Products block. +- [ ] General testing involves setting up the filter blocks and verifying the various configurations for the blocks work as expected in the editor (for settings) and the frontend (according to how the block was configured). Validate that when various filters are applied the expected results are shown in the All Products block. ### Specific changes to test for in this release diff --git a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/750.md b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/750.md index e700ae6ce2dc6..5c5dd2afc31f2 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/750.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/750.md @@ -50,7 +50,7 @@ Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github. ### Allow saved payment methods labels other than card/eCheck to display brand & last 4 digits if present. ([6177](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/6177)) -1. Install latest dev/release version of WooCommerce Payments & setup an account in [dev mode](https://woocommerce.com/document/payments/testing/dev-mode/). +1. Install latest dev/release version of WooCommerce Payments & setup an account in [dev mode](https://woocommerce.com/document/woopayments/testing-and-troubleshooting/sandbox-mode/). 2. Under WooCommerce Payments -> Settings, Enable the new WooCommerce Payments checkout experience and add new payment methods. - This _should_ have enabled Euros in Multi-Currency automatically, but if not, go to WooCommerce > Settings > Multi-Currency and enable Euros as an additional currency. 3. Save SEPA account to your customer account in one of two ways: @@ -102,8 +102,8 @@ mv wp-content/plugins/woocommerce ~/Desktop/woocommerce ln -s ~/Desktop/woocommerce wp-content/plugins/woocommerce ``` -2. Install WooCommerce Subscriptions. -3. Try to open wp-admin > WooCommerce > Home (/wp-admin/admin.php?page=wc-admin). With base branch, expect a blank page. With this PR branch, expect page loads normally. +2. Install WooCommerce Subscriptions. +3. Try to open wp-admin > WooCommerce > Home (/wp-admin/admin.php?page=wc-admin). With base branch, expect a blank page. With this PR branch, expect page loads normally. ### Make Filters Products by Price work with Active Filters block for the PHP rendered Classic Template. ([6245](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/6245)) diff --git a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/860.md b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/860.md index 9ca6910b5e7d8..75fda88120aff 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/860.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/860.md @@ -22,8 +22,8 @@ function test_package_name( $title, $index, $package ) { 5. Ensure to have one page with the Cart block and one page with the Checkout block. 6. Add two products to the cart. -7. Verify that the page with the Cart block shows the the shipping package name with line breaks instead of showing `Shipping method
test
test2`. -8. Verify that the page with the Checkout block shows the the shipping package name with line breaks instead of showing `Shipping method
test
test2`. +7. Verify that the page with the Cart block shows the shipping package name with line breaks instead of showing `Shipping method
test
test2`. +8. Verify that the page with the Checkout block shows the shipping package name with line breaks instead of showing `Shipping method
test
test2`. 9. Replace the previous code snippet with the following one: ```php @@ -62,7 +62,7 @@ to 2. Run `npm run build` to build the extension again. 3. Look up the page with the Cart or the Checkout block from the previous test case. -4. Verify that the the URL [WooCommerce.com](https://woocommerce.com/) is still visible, but that it no longer appears in the next line but in the same line. +4. Verify that the URL [WooCommerce.com](https://woocommerce.com/) is still visible, but that it no longer appears in the next line but in the same line. ### Revert "Add static class name for product-details (#6914)" [7191](https://github.com/woocommerce/woocommerce-blocks/pull/7191) diff --git a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/970.md b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/970.md index c6f260ce24e8d..322aead35031b 100644 --- a/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/970.md +++ b/plugins/woocommerce-blocks/docs/internal-developers/testing/releases/970.md @@ -307,7 +307,7 @@ With [TT2](https://wordpress.org/themes/twentytwentytwo/) and [TT3](https://word 2. Go to `WP Admin » Appearance » Editor`. 3. Click the blue `Edit` button and then click the `Styles` icon in the upper-right corner. Screenshot 2023-02-21 at 13 23 00 -4. Click on the the `Open Style Book` icon (the one that looks like an eye) +4. Click on the `Open Style Book` icon (the one that looks like an eye) Screenshot 2023-02-21 at 13 23 24 5. Verify that both the Cart and the Checkout blocks are visible. diff --git a/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md b/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md index 7e4268ce43d28..4d89bf2b9962e 100644 --- a/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md +++ b/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md @@ -5,7 +5,7 @@ - [Available field locations](#available-field-locations) - [Contact information](#contact-information) - [Address](#address) - - [Additional information](#additional-information) + - [Order information](#order-information) - [Supported field types](#supported-field-types) - [Using the API](#using-the-api) - [Options](#options) @@ -22,32 +22,32 @@ - [The select input when focused](#the-select-input-when-focused) - [Validation and sanitization](#validation-and-sanitization) - [Sanitization](#sanitization) - - [Using the `_experimental_woocommerce_blocks_sanitize_additional_field` filter](#using-the-_experimental_woocommerce_blocks_sanitize_additional_field-filter) + - [Using the `woocommerce_sanitize_additional_field` filter](#using-the-woocommerce_sanitize_additional_field-filter) - [Example of sanitization](#example-of-sanitization) - [Validation](#validation) - [Single field validation](#single-field-validation) - - [Using the `__experimental_woocommerce_blocks_validate_additional_field` action](#using-the-__experimental_woocommerce_blocks_validate_additional_field-action) + - [Using the `woocommerce_validate_additional_field` action](#using-the-woocommerce_validate_additional_field-action) - [The `WP_Error` object](#the-wp_error-object) - [Example of single-field validation](#example-of-single-field-validation) - [Multiple field validation](#multiple-field-validation) - - [Using the `__experimental_woocommerce_blocks_validate_location_{location}_fields` action](#using-the-__experimental_woocommerce_blocks_validate_location_location_fields-action) + - [Using the `woocommerce_blocks_validate_location_{location}_fields` action](#using-the-woocommerce_blocks_validate_location_location_fields-action) - [Example of location validation](#example-of-location-validation) +- [Backward compatibility](#backward-compatibility) + - [React to to saving fields](#react-to-to-saving-fields) + - [React to reading fields](#react-to-reading-fields) - [A full example](#a-full-example) A common use-case for developers and merchants is to add a new field to the Checkout form to collect additional data about a customer or their order. This document will outline the steps an extension should take to register some additional checkout fields. -> [!NOTE] -> Additional Checkout fields is still in the testing phases, use it to test the API and leave feedback in this [public discussion.](https://github.com/woocommerce/woocommerce/discussions/42995) - ## Available field locations Additional checkout fields can be registered in three different places: - Contact information - Addresses (Shipping **and** Billing) -- Additional information +- Order information A field can only be shown in one location, it is not possible to render the same field in multiple locations in the same registration. @@ -71,19 +71,148 @@ If a field is registered in the `address` location it will appear in both the sh You will also end up collecting two values for this field, one for shipping and one for billing. -### Additional information +### Order information -As part of the additional checkout fields feature, the checkout block has a new inner block called the "Additional information block". +As part of the additional checkout fields feature, the checkout block has a new inner block called the "Order information block". This block is used to render fields that aren't part of the contact information or address information, for example it may be a "How did you hear about us" field or a "Gift message" field. Fields rendered here will be saved to the order. They will not be part of the customer's saved address or account information. New orders will not have any previously used values pre-filled. -![The additional order information section containing an additional checkout field](https://github.com/woocommerce/woocommerce/assets/5656702/295b3048-a22a-4225-96b0-6b0371a7cd5f) +![The order information section containing an additional checkout field](https://github.com/woocommerce/woocommerce/assets/5656702/295b3048-a22a-4225-96b0-6b0371a7cd5f) By default, this block will render as the last step in the Checkout form, however it can be moved using the Gutenberg block controls in the editor. -![The additional order information block in the post editor"](https://github.com/woocommerce/woocommerce/assets/5656702/05a3d7d9-b3af-4445-9318-443ae2c4d7d8) +![The order information block in the post editor"](https://github.com/woocommerce/woocommerce/assets/5656702/05a3d7d9-b3af-4445-9318-443ae2c4d7d8) + +## Accessing values + +Additional fields are saved to individual meta keys in both the customer meta and order meta, you can access them using helper methods, or using the meta keys directly, we recommend using the helper methods, as they're less likely to change, can handle future migrations, and will support future enhancements (e.g. reading from different locations). + +For address fields, two values are saved: one for shipping, and one for billing. If the customer has selected 'Use same address for billing` then the values will be the same, but still saved independently of each other. + +For contact and order fields, only one value is saved per field. + +### Helper methods + +`CheckoutFields` provides a function to access values from both customers and orders, it's are `get_field_from_object`. + +To access a customer billing and/or shipping value: + +```php +use Automattic\WooCommerce\Blocks\Package; +use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; + +$field_id = 'my-plugin-namespace/my-field'; +$customer = wc()->customer; // Or new WC_Customer( $id ) +$checkout_fields = Package::container()->get( CheckoutFields::class ); +$my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' ); +$my_customer_shipping_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'shipping' ); +``` + +To access an order field: + +```php +use Automattic\WooCommerce\Blocks\Package; +use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; + +$field_id = 'my-plugin-namespace/my-field'; +$order = wc_get_order( 1234 ); +$checkout_fields = Package::container()->get( CheckoutFields::class ); +$my_order_billing_field = $checkout_fields->get_field_from_object( $field_id, $order, 'billing' ); +$my_order_shipping_field = $checkout_fields->get_field_from_object( $field_id, $order, 'shipping' ); +``` + +After an order is placed, the data saved to the customer and the data saved to the order will be the same. Customers can change the values for _future_ orders, or from within their My Account page. If you're looking at a customer value at a specific point in time (i.e. when the order was placed), access it from the order object, if you're looking for the most up to date value regardless, access it from the customer object. + +#### Guest customers + +When a guest customer places an order with additional fields, those fields will be saved to its session, so as long as the customer still has a valid session going, the values will always be there. + +#### Logged-in customers + +For logged-in customers, the value is only persisted once they place an order. Accessing a logged-in customer object during the place order lifecycle will return null or stale data. + +If you're at a place order hook, doing this will return previous data (not the currently inserted one): + +```php +$customer = new WC_Customer( $order->customer_id ); // Or new WC_Customer( 1234 ) +$my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' ); +``` + +Instead, always access the latest data if you want to run some extra validation/data-moving: + +```php +$customer = wc()->customer // This will return the current customer with its session. +$my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' ); +``` + +#### Accessing all fields + +You can use `get_all_fields_from_object` to access all additional fields saved to an order or a customer. + +```php +use Automattic\WooCommerce\Blocks\Package; +use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; + +$order = wc_get_order( 1234 ); +$checkout_fields = Package::container()->get( CheckoutFields::class ); +$order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing' ); +$order_additional_shipping_fields = $checkout_fields->get_all_fields_from_object( $order, 'shipping' ); +$order_other_additional_fields = $checkout_fields->get_all_fields_from_object( $order, 'other' ); // Contact and Order are saved in the same place under the additional group. +``` + +This will return an array of all values, it will only include fields currently registered, if you want to include fields no longer registered, you can pass a third `true` parameter. + +```php + +$order = wc_get_order( 1234 ); +$checkout_fields = Package::container()->get( CheckoutFields::class ); +$order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing' ); // array( 'my-plugin-namespace/my-field' => 'my-value' ); + +$order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing', true ); // array( 'my-plugin-namespace/my-field' => 'my-value', 'old-namespace/old-key' => 'old-value' ); +``` + +### Accessing values directly + +While not recommended, you can use the direct meta key to access certain values, this is useful for external engines or page/email builders who only provide access to meta values. + +Values are saved under a predefined prefix, this is needed to able to query fields without knowing which ID the field was registered under, for a field with key `'my-plugin-namespace/my-field'`, it's meta key will be the following if it's an address field: + +- `_wc_billing/my-plugin-namespace/my-field` +- `_wc_shipping/my-plugin-namespace/my-field` + +Or the following if it's a contact/order field: + +- `_wc_other/my-plugin-namespace/my-field`. + +Those prefixes are part of `CheckoutFields` class, and can be accessed using the following constants: + +```php +echo ( CheckoutFields::BILLING_FIELDS_PREFIX ); // _wc_billing/ +echo ( CheckoutFields::SHIPPING_FIELDS_PREFIX ); // _wc_shipping/ +echo ( CheckoutFields::OTHER_FIELDS_PREFIX ); // _wc_other/ +``` + +`CheckoutFields` provides a couple of helpers to get the group name or key based on one or the other: + +```php +CheckoutFields::get_group_name( "_wc_billing" ); // "billing" +CheckoutFields::get_group_name( "_wc_billing/" ); // "billing" + +CheckoutFields::get_group_key( "shipping" ); // "_wc_shipping/" +``` + +Use cases here would be to build the key name to access the meta directly: + +```php +$key = CheckoutFields::get_group_key( "other" ) . 'my-plugin/is-opt-in'; +$opted_in = get_user_meta( 123, $key, true ) === "1" ? true : false; +``` + +#### Checkboxes values + +When accessing a checkbox values directly, it will either return `"1"` for true, `"0"` for false, or `""` if the value doesn't exist, only the provided functions will sanitize that to a boolean. ## Supported field types @@ -97,7 +226,7 @@ There are plans to expand this list, but for now these are the types available. ## Using the API -To register additional checkout fields you must use the `__experimental_woocommerce_blocks_register_checkout_field` function. +To register additional checkout fields you must use the `woocommerce_register_additional_checkout_field` function. It is recommended to run this function after the `woocommerce_blocks_loaded` action. @@ -114,7 +243,7 @@ These options apply to all field types (except in a few circumstances which are | `id` | The field's ID. This should be a unique identifier for your field. It is composed of a namespace and field name separated by a `/`. | Yes | `plugin-namespace/how-did-you-hear` | No default - this must be provided. | | `label` | The label shown on your field. This will be the placeholder too. | Yes | `How did you hear about us?` | No default - this must be provided. | | `optionalLabel` | The label shown on your field if it is optional. This will be the placeholder too. | No | `How did you hear about us? (Optional)` | The default value will be the value of `label` with `(optional)` appended. | -| `location` | The location to render your field. | Yes | `contact`, `address`, or `additional` | No default - this must be provided. | +| `location` | The location to render your field. | Yes | `contact`, `address`, or `order` | No default - this must be provided. | | `type` | The type of field you're rendering. It defaults to `text` and must match one of the supported field types. | No | `text`, `select`, or `checkbox` | `text` | | `attributes` | An array of additional attributes to render on the field's input element. This is _not_ supported for `select` fields. | No | `[ 'data-custom-data' => 'my-custom-data' ]` | `[]` | | `sanitize_callback` | A function called to sanitize the customer provided value when posted. | No | See example below | By default the field's value is returned unchanged. | @@ -212,7 +341,7 @@ This example demonstrates rendering a text field in the address section: add_action( 'woocommerce_blocks_loaded', function() { - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/gov-id', 'label' => 'Government ID', @@ -255,7 +384,7 @@ This example demonstrates rendering a checkbox field in the contact information add_action( 'woocommerce_blocks_loaded', function() { - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/marketing-opt-in', 'label' => 'Do you want to subscribe to our newsletter?', @@ -275,17 +404,17 @@ Note that because an `optionalLabel` was not supplied, the string `(optional)` i ### Rendering a select field -This example demonstrates rendering a select field in the additional information section: +This example demonstrates rendering a select field in the order information section: ```php add_action( 'woocommerce_blocks_loaded', function() { - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/how-did-you-hear-about-us', 'label' => 'How did you hear about us?', - 'location' => 'additional', + 'location' => 'order', 'type' => 'select', 'options' => [ [ @@ -311,7 +440,7 @@ add_action( ); ``` -This results in the additional information section being rendered like so: +This results in the order information section being rendered like so: ### The select input before being focused @@ -336,9 +465,9 @@ These actions happen in two places: Sanitization is used to ensure the value of a field is in a specific format. An example is when taking a government ID, you may want to format it so that all letters are capitalized and there are no spaces. At this point, the value should **not** be checked for _validity_. That will come later. This step is only intended to set the field up for validation. -#### Using the `_experimental_woocommerce_blocks_sanitize_additional_field` filter +#### Using the `woocommerce_sanitize_additional_field` filter -To run a custom sanitization function for a field you can use the `sanitize_callback` function on registration, or the `__experimental_woocommerce_blocks_sanitize_additional_field` filter. +To run a custom sanitization function for a field you can use the `sanitize_callback` function on registration, or the `woocommerce_sanitize_additional_field` filter. | Argument | Type | Description | |--------------|-------------------|-------------------------------------------------------------------------| @@ -351,7 +480,7 @@ This example shows how to remove whitespace and capitalize all letters in the ex ```php add_action( - '_experimental_woocommerce_blocks_sanitize_additional_field', + 'woocommerce_sanitize_additional_field', function ( $field_value, $field_key ) { if ( 'namespace/gov-id' === $field_key ) { $field_value = str_replace( ' ', '', $field_key ); @@ -370,9 +499,9 @@ There are two phases of validation in the additional checkout fields system. The #### Single field validation -##### Using the `__experimental_woocommerce_blocks_validate_additional_field` action +##### Using the `woocommerce_validate_additional_field` action -When the `__experimental_woocommerce_blocks_validate_additional_field` action is fired the callback receives the field's key, the field's value, and a `WP_Error` object. +When the `woocommerce_validate_additional_field` action is fired the callback receives the field's key, the field's value, and a `WP_Error` object. To add validation errors to the response, use the [`WP_Error::add`](https://developer.wordpress.org/reference/classes/wp_error/add/) method. @@ -392,7 +521,7 @@ The below example shows how to apply custom validation to the `namespace/gov-id` ```php add_action( -'__experimental_woocommerce_blocks_validate_additional_field', +'woocommerce_validate_additional_field', function ( WP_Error $errors, $field_key, $field_value ) { if ( 'namespace/gov-id' === $field_key ) { $match = preg_match( '/[A-Z0-9]{5}/', $field_value ); @@ -412,13 +541,13 @@ If no validation errors are encountered the function can just return void. #### Multiple field validation -There are cases where the validity of a field depends on the value of another field, for example validating the format of a government ID based on what country the shopper is in. In this case, validating only single fields (as above) is not sufficient as the country may be unknown during the `__experimental_woocommerce_blocks_validate_additional_field` action. +There are cases where the validity of a field depends on the value of another field, for example validating the format of a government ID based on what country the shopper is in. In this case, validating only single fields (as above) is not sufficient as the country may be unknown during the `woocommerce_validate_additional_field` action. To solve this, it is possible to validate a field in the context of the location it renders in. The other fields in that location will be passed to this action. -##### Using the `__experimental_woocommerce_blocks_validate_location_{location}_fields` action +##### Using the `woocommerce_blocks_validate_location_{location}_fields` action -This action will be fired for each location that additional fields can render in (`address`, `contact`, and `additional`). For `address` it fires twice, once for the billing address and once for the shipping address. +This action will be fired for each location that additional fields can render in (`address`, `contact`, and `order`). For `address` it fires twice, once for the billing address and once for the shipping address. The callback receives the keys and values of the other additional fields in the same location. @@ -428,18 +557,18 @@ It is important to note that any fields rendered in other locations will not be |----------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `$errors` | `WP_Error` | An error object containing errors that were already encountered while processing the request. If no errors were added yet, it will still be a `WP_Error` object but it will be empty. | | `$fields` | `array` | The fields rendered in this locations. | -| `$group` | `'billing'\|'shipping'\|''` | If the action is for the address location, the type of address will be set here. If it is for contact or addiotional, this will be an empty string. | +| `$group` | `'billing'\|'shipping'\|'other'` | If the action is for the address location, the type of address will be set here. If it is for contact or order, this will be 'other'. | There are several places where these hooks are fired. - When checking out using the Checkout block or Store API. - - `__experimental_woocommerce_blocks_validate_location_address_fields` (x2) - - `__experimental_woocommerce_blocks_validate_location_contact_fields` - - `__experimental_woocommerce_blocks_validate_location_additional_fields` + - `woocommerce_blocks_validate_location_address_fields` (x2) + - `woocommerce_blocks_validate_location_contact_fields` + - `woocommerce_blocks_validate_location_other_fields` - When updating addresses in the "My account" area - - `__experimental_woocommerce_blocks_validate_location_address_fields` (**x1** - only the address being edited) + - `woocommerce_blocks_validate_location_address_fields` (**x1** - only the address being edited) - When updating the "Account details" section in the "My account" area - - `__experimental_woocommerce_blocks_validate_location_contact_fields` + - `woocommerce_blocks_validate_location_contact_fields` ##### Example of location validation @@ -449,7 +578,7 @@ The example below illustrates how to verify that the value of the confirmation f ```php add_action( - '__experimental_woocommerce_blocks_validate_location_address_fields', + 'woocommerce_blocks_validate_location_address_fields', function ( \WP_Error $errors, $fields, $group ) { if ( $fields['namespace/gov-id'] !== $fields['namespace/confirm-gov-id'] ) { $errors->add( 'gov_id_mismatch', 'Please ensure your government ID matches the confirmation.' ); @@ -460,7 +589,90 @@ add_action( ); ``` -If these fields were rendered in the "contact" location instead, the code would be the same except the hook used would be: `__experimental_woocommerce_blocks_validate_location_contact_fields`. +If these fields were rendered in the "contact" location instead, the code would be the same except the hook used would be: `woocommerce_blocks_validate_location_contact_fields`. + +## Backward compatibility + +Due to technical reasons, it's not yet possible to specify the meta key for fields, as we want them to be prefixed and managed. Plugins with existing fields in shortcode Checkout can be compatible and react to reading and saving fields using hooks. + +Assuming 2 fields, named `my-plugin-namespace/address-field` in the address step and `my-plugin-namespace/my-other-field` in the order step, you can: + +### React to to saving fields + +You can react to those fields being saved by hooking into `woocommerce_set_additional_field_value` action. + +```php +add_action( + 'woocommerce_set_additional_field_value', + function ( $key, $value, $group, $wc_object ) { + if ( 'my-plugin-namespace/address-field' !== $key ) { + return; + } + + if ( 'billing' === $group ) { + $my_plugin_address_key = 'existing_billing_address_field_key'; + } else { + $my_plugin_address_key = 'existing_shipping_address_field_key'; + } + + $wc_object->update_meta_data( $my_plugin_address_key, $value, true ); + }, + 10, + 4 +); + +add_action( + 'woocommerce_set_additional_field_value', + function ( $key, $value, $group, $wc_object ) { + if ( 'my-plugin-namespace/my-other-field' !== $key ) { + return; + } + + $my_plugin_key = 'existing_order_field_key'; + + $wc_object->update_meta_data( $my_plugin_key, $value, true ); + }, + 10, + 4 +); +``` + +This way, you can ensure existing systems will continue working and your integration will continue to work. However, ideally, you should migrate your existing data and systems to use the new meta fields. + + +### React to reading fields + +You can use the `woocommerce_get_default_value_for_{$key}` filters to provide a different default value (a value coming from another meta field for example): + +```php +add_filter( + "woocommerce_blocks_get_default_value_for_my-plugin-namespace/address-field", + function ( $value, $group, $wc_object ) { + + if ( 'billing' === $group ) { + $my_plugin_key = 'existing_billing_address_field_key'; + } else { + $my_plugin_key = 'existing_shipping_address_field_key'; + } + + return $wc_object->get_meta( $my_plugin_key ); + }, + 10, + 3 +); + +add_filter( + "woocommerce_blocks_get_default_value_for_my-plugin-namespace/my-other-field", + function ( $value, $group, $wc_object ) { + + $my_plugin_key = 'existing_order_field_key'; + + return $wc_object->get_meta( $my_plugin_key ); + }, + 10, + 3 +); +``` ## A full example @@ -472,7 +684,7 @@ This example is just a combined version of the examples shared above. add_action( 'woocommerce_blocks_loaded', function() { - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/gov-id', 'label' => 'Government ID', @@ -485,7 +697,7 @@ add_action( ), ), ); - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'namespace/confirm-gov-id', 'label' => 'Confirm government ID', @@ -500,7 +712,7 @@ add_action( ); add_action( - '_experimental_woocommerce_blocks_sanitize_additional_field', + 'woocommerce_sanitize_additional_field', function ( $field_value, $field_key ) { if ( 'namespace/gov-id' === $field_key || 'namespace/confirm-gov-id' === $field_key ) { $field_value = str_replace( ' ', '', $field_key ); @@ -513,7 +725,7 @@ add_action( ); add_action( - '__experimental_woocommerce_blocks_validate_additional_field', + 'woocommerce_validate_additional_field', function ( WP_Error $errors, $field_key, $field_value ) { if ( 'namespace/gov-id' === $field_key ) { $match = preg_match( '/[A-Z0-9]{5}/', $field_value ); @@ -530,7 +742,7 @@ add_action( ); add_action( - '__experimental_woocommerce_blocks_validate_location_address_fields', + 'woocommerce_blocks_validate_location_address_fields', function ( \WP_Error $errors, $fields, $group ) { if ( $fields['namespace/gov-id'] !== $fields['namespace/confirm-gov-id'] ) { $errors->add( 'gov_id_mismatch', 'Please ensure your government ID matches the confirmation.' ); diff --git a/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/data-store/checkout.md b/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/data-store/checkout.md index fb05d722d754f..5badd9d879ec0 100644 --- a/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/data-store/checkout.md +++ b/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/data-store/checkout.md @@ -250,7 +250,7 @@ const isProcessing = store.isProcessing(); ### isAfterProcessing -When the checkout status is `AFTER_PROCESSING` this flag is true. Checkout will have this status after the the block receives the response from the server side processing request. +When the checkout status is `AFTER_PROCESSING` this flag is true. Checkout will have this status after the block receives the response from the server side processing request. #### _Returns_ diff --git a/plugins/woocommerce-blocks/package.json b/plugins/woocommerce-blocks/package.json index f966a17f75095..7775ceb64ef55 100644 --- a/plugins/woocommerce-blocks/package.json +++ b/plugins/woocommerce-blocks/package.json @@ -52,7 +52,8 @@ "build:check-assets": "rimraf build/* && cross-env ASSET_CHECK=true BABEL_ENV=default NODE_ENV=production webpack", "build:deploy": "rimraf vendor/* && cross-env WOOCOMMERCE_BLOCKS_PHASE=2 composer install --no-dev && cross-env WOOCOMMERCE_BLOCKS_PHASE=2 pnpm run build --loglevel error", "prebuild:docs": "rimraf docs/extensibility/actions.md & rimraf docs/extensibility/filters.md", - "build:docs": "./vendor/bin/wp-hooks-generator --input=src --output=bin/hook-docs/data && node ./bin/hook-docs", + "build:docs": "./vendor/bin/wp-hooks-generator --input=src --output=bin/hook-docs/data && node ./bin/hook-docs && pnpm build:docs:block-references", + "build:docs:block-references": "node ./bin/gen-block-list-doc.js", "postbuild:docs": "./bin/add-doc-footer.sh", "changelog:zenhub": "node ./bin/changelog --changelogSrcType='ZENHUB_RELEASE'", "change-versions": "source ./bin/change-versions.sh", @@ -100,7 +101,7 @@ "env:restart": "pnpm run wp-env clean all && pnpm run wp-env start && ./tests/e2e/bin/test-env-setup.sh", "env:stop": "pnpm run wp-env stop", "test:help": "wp-scripts test-unit-js --help", - "test:performance": "pnpm run wp-env:config && cross-env NODE_CONFIG_DIR=tests/e2e-jest/config wp-scripts test-e2e --config tests/e2e-jest/config/jest.performance.config.js -- performance", + "test:performance": "sh ./bin/check-env.sh && pnpm playwright test --config=tests/e2e/playwright.performance.config.ts", "test:update": "wp-scripts test-unit-js --updateSnapshot --config tests/js/jest.config.json", "test:watch": "pnpm run test -- --watch", "ts:check": "tsc --build", @@ -217,17 +218,16 @@ "cssnano": "5.1.12", "deep-freeze": "0.0.1", "dotenv": "^16.3.1", - "eslint-import-resolver-typescript": "3.2.4", + "eslint-import-resolver-typescript": "3.6.1", "eslint-import-resolver-webpack": "0.13.2", "eslint-plugin-import": "2.28.1", - "eslint-plugin-playwright": "0.15.3", + "eslint-plugin-playwright": "1.6.0", "eslint-plugin-rulesdir": "^0.2.2", "eslint-plugin-storybook": "^0.6.15", "eslint-plugin-woocommerce": "file:bin/eslint-plugin-woocommerce", "eslint-plugin-you-dont-need-lodash-underscore": "6.12.0", "expect-puppeteer": "6.1.1", - "fast-xml-parser": "4.2.4", - "follow-redirects": "1.15.1", + "fast-xml-parser": "4.2.5", "fs-extra": "11.1.1", "gh-pages": "4.0.0", "github-label-sync": "^2.3.1", @@ -268,7 +268,7 @@ "terser-webpack-plugin": "5.3.6", "typescript": "5.3.2", "utility-types": "3.10.0", - "webpack": "5.88.2", + "webpack": "5.91.0", "webpack-bundle-analyzer": "4.7.0", "webpack-cli": "5.1.4", "wireit": "0.14.3", @@ -276,10 +276,11 @@ "zenhub-api": "0.2.0" }, "engines": { - "node": "^16.14.1", + "node": "^20.11.1", "pnpm": "^8.12.1" }, "dependencies": { + "@ariakit/react": "^0.4.4", "@dnd-kit/core": "^6.1.0", "@dnd-kit/modifiers": "^6.0.1", "@dnd-kit/sortable": "^7.0.2", @@ -302,7 +303,7 @@ "classnames": "^2.3.2", "compare-versions": "4.1.3", "config": "3.3.7", - "dataloader": "2.1.0", + "dataloader": "2.2.2", "deepsignal": "1.3.6", "dinero.js": "1.9.1", "dompurify": "^2.4.7", diff --git a/plugins/woocommerce-blocks/packages/checkout/blocks-registry/types.ts b/plugins/woocommerce-blocks/packages/checkout/blocks-registry/types.ts index 1a4ec9d1bac1c..543612dde4264 100644 --- a/plugins/woocommerce-blocks/packages/checkout/blocks-registry/types.ts +++ b/plugins/woocommerce-blocks/packages/checkout/blocks-registry/types.ts @@ -29,7 +29,9 @@ export enum innerBlockAreas { MINI_CART_ITEMS = 'woocommerce/mini-cart-items-block', MINI_CART_FOOTER = 'woocommerce/mini-cart-footer-block', CART_ORDER_SUMMARY = 'woocommerce/cart-order-summary-block', + CART_ORDER_SUMMARY_TOTALS = 'woocommerce/cart-order-summary-totals-block', CHECKOUT_ORDER_SUMMARY = 'woocommerce/checkout-order-summary-block', + CHECKOUT_ORDER_SUMMARY_TOTALS = 'woocommerce/checkout-order-summary-totals-block', } interface CheckoutBlockOptionsMetadata extends Partial< BlockConfiguration > { diff --git a/plugins/woocommerce-blocks/packages/components/formatted-monetary-amount/index.tsx b/plugins/woocommerce-blocks/packages/components/formatted-monetary-amount/index.tsx index a81e1781f283c..6f7a5c9bcb6dd 100644 --- a/plugins/woocommerce-blocks/packages/components/formatted-monetary-amount/index.tsx +++ b/plugins/woocommerce-blocks/packages/components/formatted-monetary-amount/index.tsx @@ -34,8 +34,18 @@ export interface FormattedMonetaryAmountProps const currencyToNumberFormat = ( currency: FormattedMonetaryAmountProps[ 'currency' ] ) => { + const hasSimiliarSeparators = + currency?.thousandSeparator === currency?.decimalSeparator; + if ( hasSimiliarSeparators ) { + // eslint-disable-next-line no-console + console.warn( + 'Thousand separator and decimal separator are the same. This may cause formatting issues.' + ); + } return { - thousandSeparator: currency?.thousandSeparator, + thousandSeparator: hasSimiliarSeparators + ? '' + : currency?.thousandSeparator, decimalSeparator: currency?.decimalSeparator, fixedDecimalScale: true, prefix: currency?.prefix, diff --git a/plugins/woocommerce-blocks/packages/components/formatted-monetary-amount/test/index.js b/plugins/woocommerce-blocks/packages/components/formatted-monetary-amount/test/index.js new file mode 100644 index 0000000000000..eedfab44185a3 --- /dev/null +++ b/plugins/woocommerce-blocks/packages/components/formatted-monetary-amount/test/index.js @@ -0,0 +1,139 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import FormattedMonetaryAmount from '../index'; + +describe( 'FormattedMonetaryAmount', () => { + describe( 'separators', () => { + test( 'should add the thousand separator', () => { + render( + + ); + + expect( screen.getByText( '1.563,45 €' ) ).toBeInTheDocument(); + } ); + + test( 'should not add thousand separator', () => { + render( + + ); + expect( screen.getByText( '1563,45 €' ) ).toBeInTheDocument(); + } ); + + test( 'should remove the thousand separator when identical to the decimal one', () => { + render( + + ); + expect( console ).toHaveWarned(); + expect( screen.getByText( '1563,45 €' ) ).toBeInTheDocument(); + } ); + } ); + describe( 'suffix/prefix', () => { + test( 'should add the currency suffix', () => { + render( + + ); + expect( screen.getByText( '0,15 €' ) ).toBeInTheDocument(); + } ); + + test( 'should add the currency prefix', () => { + render( + + ); + expect( screen.getByText( '€ 0,15' ) ).toBeInTheDocument(); + } ); + } ); + + describe( 'supports different value types', () => { + test( 'should support numbers', () => { + render( + + ); + expect( screen.getByText( '15 €' ) ).toBeInTheDocument(); + } ); + + test( 'should support strings', () => { + render( + + ); + expect( screen.getByText( '€ 15' ) ).toBeInTheDocument(); + } ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/packages/components/panel/index.tsx b/plugins/woocommerce-blocks/packages/components/panel/index.tsx index 7c4e70a51e103..52e32e1a65b54 100644 --- a/plugins/woocommerce-blocks/packages/components/panel/index.tsx +++ b/plugins/woocommerce-blocks/packages/components/panel/index.tsx @@ -18,6 +18,7 @@ export interface PanelProps { hasBorder?: boolean; title: ReactNode; titleTag?: keyof JSX.IntrinsicElements; + state?: [ boolean, React.Dispatch< React.SetStateAction< boolean > > ]; } const Panel = ( { @@ -27,8 +28,13 @@ const Panel = ( { hasBorder = false, title, titleTag: TitleTag = 'div', + state, }: PanelProps ): ReactElement => { - const [ isOpen, setIsOpen ] = useState< boolean >( initialOpen ); + let [ isOpen, setIsOpen ] = useState< boolean >( initialOpen ); + // If state is managed externally, we override the internal state. + if ( Array.isArray( state ) && state.length === 2 ) { + [ isOpen, setIsOpen ] = state; + } return (
; diff --git a/plugins/woocommerce-blocks/packages/components/panel/style.scss b/plugins/woocommerce-blocks/packages/components/panel/style.scss index b85ed9c7781a9..ac4f5722bdefd 100644 --- a/plugins/woocommerce-blocks/packages/components/panel/style.scss +++ b/plugins/woocommerce-blocks/packages/components/panel/style.scss @@ -38,6 +38,7 @@ @include reset-typography(); background: transparent; box-shadow: none; + cursor: pointer; } > .wc-block-components-panel__button-icon { @@ -59,7 +60,8 @@ } // Extra classes for specificity. -.theme-twentytwentyone.theme-twentytwentyone.theme-twentytwentyone .wc-block-components-panel__button { +.theme-twentytwentyone.theme-twentytwentyone.theme-twentytwentyone +.wc-block-components-panel__button { background-color: inherit; color: inherit; } diff --git a/plugins/woocommerce-blocks/packages/components/radio-control-accordion/index.tsx b/plugins/woocommerce-blocks/packages/components/radio-control-accordion/index.tsx index 97f32c1b99b49..a8b2ac050e6aa 100644 --- a/plugins/woocommerce-blocks/packages/components/radio-control-accordion/index.tsx +++ b/plugins/woocommerce-blocks/packages/components/radio-control-accordion/index.tsx @@ -3,6 +3,7 @@ */ import classnames from 'classnames'; import { withInstanceId } from '@wordpress/compose'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -22,6 +23,8 @@ export interface RadioControlAccordionProps { content: JSX.Element; } >; selected: string | null; + // Should the selected option be highlighted with a border? + highlightChecked?: boolean; } const RadioControlAccordion = ( { @@ -31,9 +34,14 @@ const RadioControlAccordion = ( { selected, onChange, options = [], + highlightChecked = false, }: RadioControlAccordionProps ): JSX.Element | null => { const radioControlId = id || instanceId; + const selectedOptionNumber = useMemo( () => { + return options.findIndex( ( option ) => option.value === selected ); + }, [ options, selected ] ); + if ( ! options.length ) { return null; } @@ -41,6 +49,15 @@ const RadioControlAccordion = ( {
@@ -50,7 +67,13 @@ const RadioControlAccordion = ( { const checked = option.value === selected; return (
{ const instanceId = useInstanceId( RadioControl ); const radioControlId = id || instanceId; + const selectedOptionNumber = useMemo( () => { + return options.findIndex( ( option ) => option.value === selected ); + }, [ options, selected ] ); + if ( ! options.length ) { return null; } @@ -29,11 +36,21 @@ const RadioControl = ( {
{ options.map( ( option ) => ( { const { value, label, description, secondaryLabel, secondaryDescription } = option; @@ -29,6 +30,8 @@ const Option = ( { { 'wc-block-components-radio-control__option-checked': checked, + 'wc-block-components-radio-control__option--checked-option-highlighted': + checked && highlightChecked, } ) } htmlFor={ `${ name }-${ value }` } diff --git a/plugins/woocommerce-blocks/packages/components/radio-control/style.scss b/plugins/woocommerce-blocks/packages/components/radio-control/style.scss index e18e237795474..72b4305bac830 100644 --- a/plugins/woocommerce-blocks/packages/components/radio-control/style.scss +++ b/plugins/woocommerce-blocks/packages/components/radio-control/style.scss @@ -1,3 +1,103 @@ +.wc-block-components-radio-control--highlight-checked { + position: relative; + + div.wc-block-components-radio-control-accordion-option { + position: relative; + + // This ::after element is to fake a transparent border-top on each option. + // We can't just use border-top on the option itself because of the border around the entire accordion. + // Both borders have transparency so there's an overlap where the border is darker (due to adding two + // transparent colours together). Doing it with an ::after lets us bring the "border" in by one pixel on each + // side to avoid the overlap. + &::after { + content: ""; + background: $universal-border-light; + height: 1px; + right: 1px; + left: 1px; + top: 0; + position: absolute; + } + + // The first child doesn't need a fake border-top because it's handled by its parent's border-top. This stops + // a double border. + &:first-child::after { + display: none; + } + + // This rule removes the fake border-top from the selected element to prevent a double border. + &.wc-block-components-radio-control-accordion-option--checked-option-highlighted + div.wc-block-components-radio-control-accordion-option::after { + display: none; + } + } + + // Adds a "border" around the selected option. This is done with a box-shadow to prevent a double border on the left + // and right of the selected element, and top and bottom of the first/last elements. + label.wc-block-components-radio-control__option--checked-option-highlighted, + .wc-block-components-radio-control-accordion-option--checked-option-highlighted { + box-shadow: 0 0 0 2px currentColor inset; + border-radius: 4px; + } + + // Defines a border around the radio control. Cannot be done with normal CSS borders or outlines because when + // selecting an item we get a double border on the left and right. It's not possible to remove the outer border just + // for the selected element, but using a pseudo element gives us more control. + &::after { + content: ""; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + position: absolute; + border: 1px solid $universal-border-light; + border-radius: 4px; + width: 100%; + box-sizing: border-box; + } + + // Remove the top border when the first element is selected, this is so we don't get a double border with the + // box-shadow. + &.wc-block-components-radio-control--highlight-checked--first-selected::after { + border-top: 0; + margin-top: 2px; + } + + // Remove the bottom border when the last element is selected, this is so we don't get a double border with the + // box-shadow. + &.wc-block-components-radio-control--highlight-checked--last-selected::after { + margin-bottom: 2px; + border-bottom: 0; + } + + // Remove the fake border-top from the item after the selected element, this is to prevent a double border with the + // selected element's box-shadow. + .wc-block-components-radio-control__option--checked-option-highlighted + .wc-block-components-radio-control__option::after { + display: none; + } + + .wc-block-components-radio-control__option { + + // Add a fake border to the top of each radio option. This is because using CSS borders would result in an + // overlap and two transparent borders combining to make a darker pixel. This fake border allows us to bring the + // border in by one pixel on each side to avoid the overlap. + &::after { + content: ""; + background: $universal-border-light; + height: 1px; + right: 1px; + left: 1px; + top: 0; + position: absolute; + } + + // The first child doesn't need a fake border-top because it's handled by its parent's border-top. + &:first-child::after { + display: none; + } + } +} + .wc-block-components-radio-control__option { @include reset-color(); @include reset-typography(); @@ -5,7 +105,6 @@ margin: em($gap) 0; margin-top: 0; padding: 0 0 0 em($gap-larger); - position: relative; cursor: pointer; diff --git a/plugins/woocommerce-blocks/packages/components/radio-control/types.ts b/plugins/woocommerce-blocks/packages/components/radio-control/types.ts index af736c5a50b02..e30af174b9263 100644 --- a/plugins/woocommerce-blocks/packages/components/radio-control/types.ts +++ b/plugins/woocommerce-blocks/packages/components/radio-control/types.ts @@ -16,6 +16,8 @@ export interface RadioControlProps { options: RadioControlOption[]; // Is the control disabled. disabled?: boolean; + // Should the selected option be highlighted with a border? + highlightChecked?: boolean; } export interface RadioControlOptionProps { @@ -24,6 +26,8 @@ export interface RadioControlOptionProps { onChange: ( value: string ) => void; option: RadioControlOption; disabled?: boolean; + // Should the selected option be highlighted with a border? + highlightChecked?: boolean; } interface RadioControlOptionContent { diff --git a/plugins/woocommerce-blocks/packages/components/totals-wrapper/style.scss b/plugins/woocommerce-blocks/packages/components/totals-wrapper/style.scss index f378f160b8a1c..3cd84ef33e927 100644 --- a/plugins/woocommerce-blocks/packages/components/totals-wrapper/style.scss +++ b/plugins/woocommerce-blocks/packages/components/totals-wrapper/style.scss @@ -21,10 +21,8 @@ padding: 0; > * > * { - border-bottom: 1px solid $universal-border-light; padding: $gap 0; - // Removes the border for the slot inserted &::after { border-width: 0; diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/active-filters.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/active-filters.test.js deleted file mode 100644 index d44ee4b30bc2c..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/active-filters.test.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * External dependencies - */ -import { - switchBlockInspectorTab, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { - visitBlockPage, - selectBlockByName, -} from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { openSettingsSidebar } from '../../utils.js'; - -const block = { - name: 'Active Filters', - slug: 'woocommerce/active-filters', - class: '.wc-block-active-filters', - title: 'Active filters', -}; - -describe.skip( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - describe( 'attributes', () => { - beforeEach( async () => { - await openSettingsSidebar(); - await selectBlockByName( block.slug ); - await switchBlockInspectorTab( 'Settings' ); - } ); - - it( "allows changing the block's title", async () => { - const textareaSelector = - '.wp-block-woocommerce-filter-wrapper .wp-block-heading'; - await expect( page ).toFill( textareaSelector, 'New Title' ); - await expect( page ).toMatchElement( - '.wp-block-woocommerce-filter-wrapper .wp-block-heading', - { text: 'New Title' } - ); - // reset - await expect( page ).toFill( textareaSelector, block.title ); - } ); - - it( 'allows changing the Display Style', async () => { - // Click the button to convert the display style to Chips. - await expect( page ).toClick( 'button', { text: 'Chips' } ); - await expect( page ).toMatchElement( - '.wc-block-active-filters__list.wc-block-active-filters__list--chips' - ); - - // Click the button to convert the display style to List. - await expect( page ).toClick( 'button', { text: 'List' } ); - await expect( page ).not.toMatchElement( - '.wc-block-active-filters__list.wc-block-active-filters__list--chips' - ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/add-to-cart-form.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/add-to-cart-form.test.js deleted file mode 100644 index 59c857dab5801..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/add-to-cart-form.test.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * External dependencies - */ -import { - canvas, - createNewPost, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { searchForBlock } from '@wordpress/e2e-test-utils/build/inserter'; - -/** - * Internal dependencies - */ -import { - filterCurrentBlocks, - insertBlockDontWaitForInsertClose, - useTheme, - waitForCanvas, - goToTemplateEditor, -} from '../../utils.js'; - -const block = { - name: 'Add to Cart with Options', - slug: 'woocommerce/add-to-cart-form', - class: '.wc-block-add-to-cart-form', -}; - -describe( `${ block.name } Block`, () => { - it( 'can not be inserted in a post', async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - await searchForBlock( block.name ); - expect( page ).toMatch( 'No results found.' ); - } ); - - describe.skip( 'in FSE editor', () => { - useTheme( 'emptytheme' ); - - beforeEach( async () => { - await goToTemplateEditor( { - postId: 'woocommerce/woocommerce//single-product', - } ); - await waitForCanvas(); - } ); - - it( 'can be inserted in FSE area', async () => { - // We are using here the "insertBlockDontWaitForInsertClose" function because the - // tests are flickering when we use the "insertBlock" function. - await insertBlockDontWaitForInsertClose( block.name ); - await expect( canvas() ).toMatchElement( block.class ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - const filteredBlocks = await filterCurrentBlocks( - ( b ) => b.name === block.slug - ); - expect( filteredBlocks ).toHaveLength( 2 ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/all-products.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/all-products.test.js deleted file mode 100644 index 530727a5ac6e5..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/all-products.test.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; -import { merchant } from '@woocommerce/e2e-utils'; - -/** - * Internal dependencies - */ -import { - searchForBlock, - insertBlockDontWaitForInsertClose, - openWidgetEditor, - closeModalIfExists, -} from '../../utils.js'; - -const block = { - name: 'All Products', - slug: 'woocommerce/all-products', - class: '.wc-block-all-products', -}; - -describe( `${ block.name } Block`, () => { - describe( 'in page editor', () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'can only be inserted once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 1 ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - } ); - - describe( 'in widget editor', () => { - it( "can't be inserted in a widget area", async () => { - await merchant.login(); - await openWidgetEditor(); - await closeModalIfExists(); - await searchForBlock( block.name ); - const allProductsButton = await page.$x( - `//button//span[text()='${ block.name }']` - ); - - expect( allProductsButton ).toHaveLength( 0 ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/attribute-filter.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/attribute-filter.test.js deleted file mode 100644 index a8650caf7cc83..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/attribute-filter.test.js +++ /dev/null @@ -1,192 +0,0 @@ -/** - * External dependencies - */ -import { - switchBlockInspectorTab, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; - -import { - visitBlockPage, - saveOrPublish, - selectBlockByName, -} from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { openSettingsSidebar } from '../../utils.js'; - -const block = { - name: 'Filter by Attribute', - slug: 'woocommerce/attribute-filter', - class: '.wc-block-attribute-filter', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - - await page.waitForSelector( 'span.woocommerce-search-list__item-name' ); - - // eslint-disable-next-line jest/no-standalone-expect - await expect( page ).toClick( - 'span.woocommerce-search-list__item-name', - { text: 'Capacity' } - ); - - // eslint-disable-next-line jest/no-standalone-expect - await expect( page ).toClick( - 'span.woocommerce-search-list__item-name', - { text: 'Capacity' } - ); - - // eslint-disable-next-line jest/no-standalone-expect - await expect( page ).toClick( - '.wp-block-woocommerce-attribute-filter button', - { text: 'Done' } - ); - await page.waitForTimeout( 30000 ); - await page.waitForNetworkIdle(); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - it( 'renders correctly', async () => { - expect( - await page.$$eval( - '.wc-block-attribute-filter-list li', - ( attributes ) => attributes.length - ) - // our test data loads 2 for the Capacity attribute. - ).toBeGreaterThanOrEqual( 2 ); - } ); - - describe( 'Attributes', () => { - beforeEach( async () => { - await openSettingsSidebar(); - await selectBlockByName( block.slug ); - await switchBlockInspectorTab( 'Settings' ); - } ); - - it( "allows changing the block's title", async () => { - const textareaSelector = - '.wp-block-woocommerce-filter-wrapper .wp-block-heading'; - await expect( page ).toFill( textareaSelector, 'New Title' ); - await expect( page ).toMatchElement( - '.wp-block-woocommerce-filter-wrapper', - { text: 'New Title' } - ); - await expect( page ).toFill( - textareaSelector, - 'Filter by Capacity' - ); - } ); - - it( 'can hide product count', async () => { - await expect( page ).not.toMatchElement( - '.wc-filter-element-label-list-count' - ); - await expect( page ).toClick( 'label', { - text: 'Display product count', - } ); - await expect( page ).toMatchElement( - '.wc-filter-element-label-list-count' - ); - // reset - await expect( page ).toClick( 'label', { - text: 'Display product count', - } ); - } ); - - it( 'can toggle go button', async () => { - await expect( page ).not.toMatchElement( - '.wc-block-filter-submit-button' - ); - await expect( page ).toClick( 'label', { - text: "Show 'Apply filters' button", - } ); - await expect( page ).toMatchElement( - '.wc-block-filter-submit-button' - ); - // reset - await expect( page ).toClick( 'label', { - text: "Show 'Apply filters' button", - } ); - } ); - - it( 'can switch attribute', async () => { - await expect( page ).toClick( 'button', { - text: 'Content Settings', - } ); - - await expect( page ).toClick( - 'span.woocommerce-search-list__item-name', - { - text: 'Capacity', - } - ); - await page.waitForSelector( - '.wc-block-attribute-filter-list:not(.is-loading)' - ); - expect( - await page.$$eval( - '.wc-block-attribute-filter-list li', - ( reviews ) => reviews.length - ) - // Capacity has only 2 attributes - ).toEqual( 2 ); - - await expect( page ).toClick( - 'span.woocommerce-search-list__item-name', - { - text: 'Shade', - } - ); - //needed for attributes list to load correctly - await page.waitForTimeout( 1000 ); - - // reset - await expect( page ).toClick( - 'span.woocommerce-search-list__item-name', - { - text: 'Capacity', - } - ); - //needed for attributes list to load correctly - await page.waitForTimeout( 1000 ); - } ); - - it( 'renders on the frontend', async () => { - await saveOrPublish(); - const link = await page.evaluate( () => - wp.data.select( 'core/editor' ).getPermalink() - ); - await page.goto( link, { waitUntil: 'networkidle2' } ); - await page.waitForSelector( - '.wp-block-woocommerce-attribute-filter' - ); - await expect( page ).toMatchElement( - '.wp-block-woocommerce-filter-wrapper', - { - text: 'Filter by Capacity', - } - ); - - await page.waitForSelector( - '.wc-block-checkbox-list:not(.is-loading)' - ); - - expect( - await page.$$eval( - '.wc-block-attribute-filter-list li', - ( reviews ) => reviews.length - ) - // Capacity has only two attributes - ).toEqual( 2 ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/breadcrumbs.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/breadcrumbs.test.js deleted file mode 100644 index 31e1a8bddf126..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/breadcrumbs.test.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * External dependencies - */ -import { - canvas, - createNewPost, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { searchForBlock } from '@wordpress/e2e-test-utils/build/inserter'; - -/** - * Internal dependencies - */ -import { - filterCurrentBlocks, - goToSiteEditor, - insertBlockDontWaitForInsertClose, - useTheme, - waitForCanvas, -} from '../../utils.js'; - -const block = { - name: 'Store Breadcrumbs', - slug: 'woocommerce/breadcrumbs', - class: '.wc-block-breadcrumbs', -}; - -describe( `${ block.name } Block`, () => { - it( 'can not be inserted in a post', async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - await searchForBlock( block.name ); - expect( page ).toMatch( 'No results found.' ); - } ); - - describe.skip( 'in FSE editor', () => { - useTheme( 'emptytheme' ); - - beforeEach( async () => { - await goToSiteEditor(); - await waitForCanvas(); - } ); - - it( 'can be inserted in FSE area', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - await expect( canvas() ).toMatchElement( block.class ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - await insertBlockDontWaitForInsertClose( block.name ); - const filteredBlocks = await filterCurrentBlocks( - ( b ) => b.name === block.slug - ); - expect( filteredBlocks ).toHaveLength( 2 ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/catalog-sorting.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/catalog-sorting.test.js deleted file mode 100644 index 6f1e83ade01d5..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/catalog-sorting.test.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * External dependencies - */ -import { - canvas, - createNewPost, - switchUserToAdmin, - searchForBlock, -} from '@wordpress/e2e-test-utils'; - -/** - * Internal dependencies - */ -import { - filterCurrentBlocks, - goToSiteEditor, - insertBlockDontWaitForInsertClose, - useTheme, - waitForCanvas, -} from '../../utils.js'; - -const block = { - name: 'Catalog Sorting', - slug: 'woocommerce/catalog-sorting', - class: '.wc-block-catalog-sorting', -}; - -describe( `${ block.name } Block`, () => { - it( 'can not be inserted in a post', async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - await searchForBlock( block.name ); - expect( page ).toMatch( 'No results found.' ); - } ); - - describe.skip( 'in FSE editor', () => { - useTheme( 'emptytheme' ); - - beforeEach( async () => { - await goToSiteEditor(); - await waitForCanvas(); - } ); - - it( 'can be inserted in FSE area', async () => { - // We are using here the "insertBlockDontWaitForInsertClose" function because the - // tests are flickering when we use the "insertBlock" function. - await insertBlockDontWaitForInsertClose( block.name ); - - await expect( canvas() ).toMatchElement( block.class ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - await insertBlockDontWaitForInsertClose( block.name ); - const catalogStoringBlock = await filterCurrentBlocks( - ( b ) => b.name === block.slug - ); - expect( catalogStoringBlock ).toHaveLength( 2 ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/customer-account.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/customer-account.test.js deleted file mode 100644 index c706909961189..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/customer-account.test.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * External dependencies - */ -import { - switchBlockInspectorTab, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { openSettingsSidebar } from '../../utils.js'; - -const block = { - name: 'Customer account', - slug: 'woocommerce/customer-account', - class: '.wc-block-editor-customer-account', -}; - -const SELECTORS = { - icon: '.wp-block-woocommerce-customer-account svg', - label: '.wp-block-woocommerce-customer-account .label', - iconToggle: '.wc-block-editor-customer-account__icon-style-toggle', - displayDropdown: '.customer-account-display-style select', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - describe( 'attributes', () => { - beforeEach( async () => { - await openSettingsSidebar(); - await switchBlockInspectorTab( 'Settings' ); - await page.click( block.class ); - } ); - - it( 'icon options can be set to Text-only', async () => { - await expect( page ).toSelect( - SELECTORS.displayDropdown, - 'Text-only' - ); - await expect( page ).not.toMatchElement( SELECTORS.iconToggle ); - - await expect( page ).not.toMatchElement( SELECTORS.icon ); - await expect( page ).toMatchElement( SELECTORS.label ); - } ); - - it( 'icon options can be set to Icon-only', async () => { - await expect( page ).toSelect( - SELECTORS.displayDropdown, - 'Icon-only' - ); - await expect( page ).toMatchElement( SELECTORS.iconToggle ); - - await expect( page ).toMatchElement( SELECTORS.icon ); - await expect( page ).not.toMatchElement( SELECTORS.label ); - } ); - - it( 'icon options can be set to Icon and text', async () => { - await expect( page ).toSelect( - SELECTORS.displayDropdown, - 'Icon and text' - ); - await expect( page ).toMatchElement( SELECTORS.iconToggle ); - - await expect( page ).toMatchElement( SELECTORS.icon ); - await expect( page ).toMatchElement( SELECTORS.label ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/featured-category.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/featured-category.test.js deleted file mode 100644 index 23e0509874328..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/featured-category.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Featured Category', - slug: 'woocommerce/featured-category', - class: '.wc-block-featured-category', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/featured-product.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/featured-product.test.js deleted file mode 100644 index ce19fd930b1cc..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/featured-product.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Featured Product', - slug: 'woocommerce/featured-product', - class: '.wc-block-featured-product', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/handpicked-products.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/handpicked-products.test.js deleted file mode 100644 index e31e07271e3b4..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/handpicked-products.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Hand-picked Products', - slug: 'woocommerce/handpicked-products', - class: '.wc-block-handpicked-products', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/price-filter.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/price-filter.test.js deleted file mode 100644 index 0c9d3a1db967b..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/price-filter.test.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * External dependencies - */ -import { - switchBlockInspectorTab, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { - visitBlockPage, - selectBlockByName, -} from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { openSettingsSidebar } from '../../utils.js'; - -const block = { - name: 'Filter by Price', - slug: 'woocommerce/price-filter', - class: '.wp-block-woocommerce-price-filter', -}; - -describe( `${ block.name } Block`, () => { - describe( 'after compatibility notice is dismissed', () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - describe( 'Attributes', () => { - beforeEach( async () => { - await openSettingsSidebar(); - await selectBlockByName( block.slug ); - await switchBlockInspectorTab( 'Settings' ); - } ); - - it( "allows changing the block's title", async () => { - const textareaSelector = - '.wp-block-woocommerce-filter-wrapper .wp-block-heading'; - - await expect( page ).toFill( textareaSelector, 'New Title' ); - - await page.click( block.class ); - - await expect( page ).toMatchElement( textareaSelector, { - text: 'New Title', - } ); - - await expect( page ).toFill( - textareaSelector, - 'Filter by price' - ); - } ); - - it( 'allows changing the Display Style', async () => { - // Turn the display style to Price Range: Text - await expect( page ).toClick( 'button', { text: 'Text' } ); - - await page.waitForSelector( - '.wc-block-price-filter__range-text' - ); - await expect( page ).toMatchElement( - '.wc-block-price-filter__range-text' - ); - // Turn the display style to Price Range: Editable - await expect( page ).toClick( 'button', { - text: 'Editable', - } ); - - await expect( page ).not.toMatchElement( - '.wc-block-price-filter__range-text' - ); - } ); - - it( 'allows you to toggle filter button', async () => { - await expect( page ).toClick( 'label', { - text: "Show 'Apply filters' button", - } ); - await expect( page ).toMatchElement( - 'button.wc-block-filter-submit-button.wc-block-price-filter__button' - ); - await expect( page ).toClick( 'label', { - text: "Show 'Apply filters' button", - } ); - } ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-best-sellers.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-best-sellers.test.js deleted file mode 100644 index 82f64673ed948..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-best-sellers.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Best Selling Products', - slug: 'woocommerce/product-best-sellers', - class: '.wc-block-product-best-sellers', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-categories.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-categories.test.js deleted file mode 100644 index 6b607a0b13a14..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-categories.test.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Product Categories List', - slug: 'woocommerce/product-categories', - class: '.wc-block-product-categories', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-category.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-category.test.js deleted file mode 100644 index 16808b6f78678..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-category.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Products by Category', - slug: 'woocommerce/product-category', - class: '.wc-block-products-category', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-new.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-new.test.js deleted file mode 100644 index 8d45f9c307cab..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-new.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Newest Products', - slug: 'woocommerce/product-new', - class: '.wc-block-product-new', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-on-sale.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-on-sale.test.js deleted file mode 100644 index 833bfd294ff30..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-on-sale.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'On Sale Products', - slug: 'woocommerce/product-on-sale', - class: '.wc-block-product-on-sale', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query.test.ts deleted file mode 100644 index 669a3f668397e..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * External dependencies - */ -import { - getAllBlocks, - switchUserToAdmin, - canvas, - openListView, -} from '@wordpress/e2e-test-utils'; -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { - insertBlockDontWaitForInsertClose, - openSettingsSidebar, -} from '../../utils'; - -const block = { - name: 'Products (Beta)', - slug: 'woocommerce/product-query', - class: '.wp-block-query', -}; - -// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. -describe.skip( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - /** - * We changed the “Show only products on sale” from a top-level toggle - * setting to a product filter, but tests for them haven't been updated - * yet. We will fix these tests in a follow-up PR. - */ - it.skip( 'Editor preview shows only on sale products after enabling `Show only products on sale`', async () => { - await visitBlockPage( `${ block.name } Block` ); - const canvasEl = canvas(); - await openSettingsSidebar(); - await openListView(); - await page.click( - '.block-editor-list-view-block__contents-container a.components-button' - ); - const [ onSaleToggle ] = await page.$x( - '//label[text()="Show only products on sale"]' - ); - await onSaleToggle.click(); - await canvasEl.waitForSelector( `${ block.class } > p` ); - await canvasEl.waitForSelector( - `${ block.class } > ul.wp-block-post-template` - ); - const products = await canvasEl.$$( - `${ block.class } ul.wp-block-post-template > li.block-editor-block-preview__live-content` - ); - expect( products ).toHaveLength( 1 ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/advanced-filters.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/advanced-filters.test.ts deleted file mode 100644 index bc606f59b5978..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/advanced-filters.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * External dependencies - */ -import { ensureSidebarOpened, canvas } from '@wordpress/e2e-test-utils'; -import { - saveOrPublish, - selectBlockByName, - findToolsPanelWithTitle, - getFixtureProductsData, - shopper, - getToggleIdByLabel, -} from '@woocommerce/blocks-test-utils'; -import { ElementHandle } from 'puppeteer'; -import { setCheckbox } from '@woocommerce/e2e-utils'; - -/** - * Internal dependencies - */ -import { - block, - SELECTORS, - resetProductQueryBlockPage, - toggleAdvancedFilter, - getPreviewProducts, - getFrontEndProducts, - clearSelectedTokens, - selectToken, -} from './common'; - -// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. -describe.skip( `${ block.name } > Advanced Filters`, () => { - let $productFiltersPanel: ElementHandle< Node >; - const defaultCount = getFixtureProductsData().length; - const saleCount = getFixtureProductsData( 'sale_price' ).length; - const outOfStockCount = getFixtureProductsData( 'stock_status' ).filter( - ( status: string ) => status === 'outofstock' - ).length; - - beforeEach( async () => { - /** - * Reset the block page before each test to ensure the block is - * inserted in a known state. This is also needed to ensure each - * test can be run individually. - */ - await resetProductQueryBlockPage(); - await ensureSidebarOpened(); - await selectBlockByName( block.slug ); - $productFiltersPanel = await findToolsPanelWithTitle( - 'Advanced Filters' - ); - } ); - - /** - * Reset the content of Product Query Block page after this test suite - * to avoid breaking other tests. - */ - afterAll( async () => { - await resetProductQueryBlockPage(); - } ); - - it( 'Editor preview shows all products by default', async () => { - expect( await getPreviewProducts() ).toHaveLength( defaultCount ); - } ); - - it( 'On the front end, blocks shows all products by default', async () => { - expect( await getPreviewProducts() ).toHaveLength( defaultCount ); - } ); - - describe( 'Sale Status', () => { - it( 'Sale status is disabled by default', async () => { - await expect( $productFiltersPanel ).not.toMatch( - 'Show only products on sale' - ); - } ); - - it( 'Can add and remove Sale Status filter', async () => { - await toggleAdvancedFilter( 'Sale status' ); - await expect( $productFiltersPanel ).toMatch( - 'Show only products on sale' - ); - await toggleAdvancedFilter( 'Sale status' ); - await expect( $productFiltersPanel ).not.toMatch( - 'Show only products on sale' - ); - } ); - - it( 'Enable Sale Status > Editor preview shows only on sale products', async () => { - await toggleAdvancedFilter( 'Sale status' ); - await setCheckbox( - await getToggleIdByLabel( 'Show only products on sale' ) - ); - expect( await getPreviewProducts() ).toHaveLength( saleCount ); - } ); - - it( 'Enable Sale Status > On the front end, block shows only on sale products', async () => { - await toggleAdvancedFilter( 'Sale status' ); - await setCheckbox( - await getToggleIdByLabel( 'Show only products on sale' ) - ); - await canvas().waitForSelector( SELECTORS.productsGrid ); - await saveOrPublish(); - await shopper.block.goToBlockPage( block.name ); - expect( await getFrontEndProducts() ).toHaveLength( saleCount ); - } ); - } ); - - describe( 'Stock Status', () => { - it( 'Stock status is enabled by default', async () => { - await expect( $productFiltersPanel ).toMatchElement( - SELECTORS.formTokenField.label, - { text: 'Stock status' } - ); - } ); - - it( 'Can add and remove Stock Status filter', async () => { - await toggleAdvancedFilter( 'Stock status' ); - await expect( $productFiltersPanel ).not.toMatchElement( - SELECTORS.formTokenField.label, - { text: 'Stock status' } - ); - await toggleAdvancedFilter( 'Stock status' ); - await expect( $productFiltersPanel ).toMatchElement( - SELECTORS.formTokenField.label, - { text: 'Stock status' } - ); - } ); - - it( 'All statuses are enabled by default', async () => { - await expect( $productFiltersPanel ).toMatch( 'In stock' ); - await expect( $productFiltersPanel ).toMatch( 'Out of stock' ); - await expect( $productFiltersPanel ).toMatch( 'On backorder' ); - } ); - - it( 'Set Stock status to Out of stock > Editor preview shows only out-of-stock products', async () => { - await clearSelectedTokens( $productFiltersPanel ); - await selectToken( 'Stock status', 'Out of stock' ); - expect( await getPreviewProducts() ).toHaveLength( - outOfStockCount - ); - } ); - - it( 'Set Stock status to Out of stock > On the front end, block shows only out-of-stock products', async () => { - await clearSelectedTokens( $productFiltersPanel ); - await selectToken( 'Stock status', 'Out of stock' ); - await canvas().waitForSelector( SELECTORS.productsGrid ); - await saveOrPublish(); - await shopper.block.goToBlockPage( block.name ); - expect( await getFrontEndProducts() ).toHaveLength( - outOfStockCount - ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/atomic-blocks.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/atomic-blocks.test.ts deleted file mode 100644 index 16390ea5787f4..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/atomic-blocks.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * External dependencies - */ -import { canvas } from '@wordpress/e2e-test-utils'; -import { - saveOrPublish, - shopper, - insertInnerBlock, - getFixtureProductsData, -} from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { - block, - SELECTORS, - resetProductQueryBlockPage, - getProductElementNodesCount, - getEditorProductElementNodesCount, -} from './common'; - -// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. -describe.skip( `${ block.name } > Atomic blocks`, () => { - beforeEach( async () => { - await resetProductQueryBlockPage(); - } ); - - afterAll( async () => { - await resetProductQueryBlockPage(); - } ); - - it( 'Can add the Add to Cart Button block and render it on the front end', async () => { - await page.waitForSelector( SELECTORS.productButton ); - await expect( canvas() ).toMatchElement( SELECTORS.productButton, { - text: 'Add to cart', - } ); - await insertInnerBlock( 'Add to Cart Button', 'core/post-template' ); - expect( - await getEditorProductElementNodesCount( SELECTORS.productButton ) - ).toEqual( 2 ); - - await shopper.block.goToBlockPage( block.name ); - await page.waitForSelector( SELECTORS.productButton ); - await expect( page ).toClick( 'button', { - text: 'Add to cart', - } ); - await shopper.block.goToCart(); - await page.waitForSelector( '.wc-block-cart-items__row' ); - expect( - await getProductElementNodesCount( SELECTORS.cartItemRow ) - ).toEqual( 1 ); - } ); - - it( 'Can add the Product Image block', async () => { - await page.waitForSelector( SELECTORS.productImage ); - await insertInnerBlock( 'Product Image', 'core/post-template' ); - expect( - await getEditorProductElementNodesCount( SELECTORS.productImage ) - ).toEqual( 2 ); - } ); - - it( 'Can add the Product Price block and render it on the front end', async () => { - const fixturePrices = getFixtureProductsData( 'regular_price' ); - const testPrice = - fixturePrices[ Math.floor( Math.random() * fixturePrices.length ) ]; - await page.waitForSelector( SELECTORS.productPrice ); - await expect( canvas() ).toMatchElement( SELECTORS.productPrice, { - text: testPrice, - } ); - await insertInnerBlock( 'Product Price', 'core/post-template' ); - expect( - await getEditorProductElementNodesCount( SELECTORS.productPrice ) - ).toEqual( 2 ); - - await shopper.block.goToBlockPage( block.name ); - await page.waitForSelector( SELECTORS.productPrice ); - await expect( page ).toMatchElement( SELECTORS.productPrice, { - text: testPrice, - } ); - } ); - - it( 'Can add the Product Ratings block and render it on the front end', async () => { - await insertInnerBlock( 'Product Rating', 'core/post-template' ); - expect( - await getEditorProductElementNodesCount( SELECTORS.productRating ) - ).toEqual( 1 ); - await saveOrPublish(); - await shopper.block.goToBlockPage( block.name ); - expect( - await getProductElementNodesCount( SELECTORS.productRating ) - ).toEqual( getFixtureProductsData().length ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/common.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/common.ts deleted file mode 100644 index 7cdba34609a1f..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/common.ts +++ /dev/null @@ -1,204 +0,0 @@ -/** - * We choose to name this file as common instead of splitting it to multiple - * files like constants.ts and utils.ts because we want to keep the file - * structure as simple as possible. We also want to distinguish the local - * utilities like resetProductQueryBlockPage with our e2e utilites in - * `@woocommerce/blocks-test-utils`. We think exporting local utilites from - * a different file name is an simple but effective way to achieve that goal. - */ - -/** - * External dependencies - */ -import { canvas, setPostContent } from '@wordpress/e2e-test-utils'; -import { - shopper, - visitBlockPage, - saveOrPublish, - findToolsPanelWithTitle, - getFormElementIdByLabel, - insertShortcodeBlock, -} from '@woocommerce/blocks-test-utils'; -import { ElementHandle } from 'puppeteer'; - -/** - * Internal dependencies - */ -import { waitForCanvas } from '../../../utils'; -import fixture from '../__fixtures__/products-beta.fixture.json'; - -export const block = { - name: 'Products (Beta)', - slug: 'core/query', - class: '.wp-block-query', -}; - -/** - * Selectors used for interacting with the blocks. These selectors can be - * changed upstream in Gutenberg, so we scope them here for maintainability. - * - * There are also some labels that are used repeatedly, but we don't scope them - * in favor of readability. Unlike selectors, those label are visible to end - * users, so it's easier to understand what's going on if we don't scope them. - * Those labels can get upated in the future, but the tests will fail and we'll - * know to update them, again the update process is easier than selector as the - * label is visible to end users. - */ -export const SELECTORS = { - advancedFiltersDropdownButton: ( - { expanded }: { expanded: boolean } = { expanded: false } - ) => - `.components-tools-panel-header .components-dropdown-menu button[aria-expanded="${ expanded }"]`, - advancedFiltersDropdown: - '.components-dropdown-menu__menu[aria-label="Advanced Filters options"]', - advancedFiltersDropdownItem: '.components-menu-item__button', - productsGrid: `${ block.class } ul.wp-block-post-template`, - productsGridLoading: `${ block.class } p.wp-block-post-template`, - productsGridItem: `${ block.class } ul.wp-block-post-template > li`, - formTokenField: { - label: '.components-form-token-field__label', - removeToken: '.components-form-token-field__remove-token', - suggestionsList: '.components-form-token-field__suggestions-list', - firstSuggestion: - '.components-form-token-field__suggestions-list > li:first-child', - }, - productButton: '.wc-block-components-product-button', - productPrice: '.wc-block-components-product-price', - productRating: '.wc-block-components-product-rating', - productImage: '.wc-block-components-product-image', - cartItemRow: '.wc-block-cart-items__row', - shortcodeProductsGrid: `${ block.class } ul.wp-block-post-template`, - shortcodeProductsGridItem: `${ block.class } ul.wp-block-post-template > li`, - customSelectControl: { - button: '.components-custom-select-control__button', - menu: ( { hidden }: { hidden: boolean } = { hidden: true } ) => - `.components-custom-select-control__menu[aria-hidden="${ hidden }"]`, - }, - visuallyHiddenComponents: '.components-visually-hidden', -}; - -export const goToProductQueryBlockPage = async () => { - await shopper.block.goToBlockPage( block.name ); -}; - -export const resetProductQueryBlockPage = async () => { - await visitBlockPage( `${ block.name } Block` ); - await waitForCanvas(); - await setPostContent( fixture.pageContent ); - await canvas().waitForSelector( SELECTORS.productsGrid ); - await saveOrPublish(); -}; - -export const getPreviewProducts = async (): Promise< ElementHandle[] > => { - await canvas().waitForSelector( SELECTORS.productsGrid ); - return await canvas().$$( - `${ SELECTORS.productsGridItem }.block-editor-block-preview__live-content` - ); -}; - -export const getFrontEndProducts = async (): Promise< ElementHandle[] > => { - await canvas().waitForSelector( SELECTORS.productsGrid ); - return await canvas().$$( SELECTORS.productsGridItem ); -}; - -export const getShortcodeProducts = async (): Promise< ElementHandle[] > => { - await canvas().waitForSelector( SELECTORS.shortcodeProductsGrid ); - return await canvas().$$( SELECTORS.shortcodeProductsGridItem ); -}; - -export const toggleAdvancedFilter = async ( filterName: string ) => { - const $advancedFiltersPanel = await findToolsPanelWithTitle( - 'Advanced Filters' - ); - await expect( $advancedFiltersPanel ).toClick( - SELECTORS.advancedFiltersDropdownButton() - ); - await canvas().waitForSelector( SELECTORS.advancedFiltersDropdown ); - await expect( canvas() ).toClick( SELECTORS.advancedFiltersDropdownItem, { - text: filterName, - } ); - await expect( $advancedFiltersPanel ).toClick( - SELECTORS.advancedFiltersDropdownButton( { expanded: true } ) - ); -}; - -export const clearSelectedTokens = async ( $panel: ElementHandle< Node > ) => { - const tokenRemoveButtons = await $panel.$$( - SELECTORS.formTokenField.removeToken - ); - for ( const el of tokenRemoveButtons ) { - await el.click(); - } -}; - -export const selectToken = async ( formLabel: string, optionLabel: string ) => { - const $stockStatusInput = await canvas().$( - await getFormElementIdByLabel( - formLabel, - SELECTORS.formTokenField.label - ) - ); - await $stockStatusInput.focus(); - await canvas().keyboard.type( optionLabel ); - const firstSuggestion = await canvas().waitForSelector( - SELECTORS.formTokenField.firstSuggestion - ); - await firstSuggestion.click(); - await canvas().waitForSelector( SELECTORS.productsGrid ); -}; - -export const getProductElementNodesCount = async ( selector: string ) => { - return await page.$$eval( selector, ( elements ) => elements.length ); -}; - -export const getEditorProductElementNodesCount = async ( selector: string ) => { - return await getProductElementNodesCount( - `li.block-editor-block-list__layout ${ selector }` - ); -}; - -export const getProductTitle = async ( - product: ElementHandle -): Promise< string > => { - return ( - ( await product.$eval( - '.wp-block-post-title', - ( el ) => el.textContent - ) ) || '' - ); -}; - -export const setupEditorFrontendComparison = async () => { - const previewProducts = await Promise.all( - ( - await getPreviewProducts() - ).map( async ( product ) => await getProductTitle( product ) ) - ); - await goToProductQueryBlockPage(); - await canvas().waitForSelector( SELECTORS.productsGrid ); - const frontEndProducts = await Promise.all( - ( - await getFrontEndProducts() - ).map( async ( product ) => await getProductTitle( product ) ) - ); - return { previewProducts, frontEndProducts }; -}; - -export const setupProductQueryShortcodeComparison = async ( - shortcode: string -) => { - await insertShortcodeBlock( shortcode ); - await saveOrPublish(); - await goToProductQueryBlockPage(); - const productQueryProducts = await Promise.all( - ( - await getFrontEndProducts() - ).map( async ( product ) => await getProductTitle( product ) ) - ); - const shortcodeProducts = await Promise.all( - ( - await getShortcodeProducts() - ).map( async ( product ) => await getProductTitle( product ) ) - ); - return { productQueryProducts, shortcodeProducts }; -}; diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/popular-filters.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/popular-filters.test.ts deleted file mode 100644 index 98e4de7d6246d..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-query/popular-filters.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * External dependencies - */ -import { ElementHandle } from 'puppeteer'; -import { - canvas, - ensureSidebarOpened, - findSidebarPanelWithTitle, -} from '@wordpress/e2e-test-utils'; -import { - selectBlockByName, - visitBlockPage, - saveOrPublish, -} from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { - block, - SELECTORS, - resetProductQueryBlockPage, - setupProductQueryShortcodeComparison, - setupEditorFrontendComparison, -} from './common'; - -const getPopularFilterPanel = async () => { - await ensureSidebarOpened(); - await selectBlockByName( block.slug ); - return await findSidebarPanelWithTitle( 'Popular Filters' ); -}; - -const getCurrentPopularFilter = async ( $panel: ElementHandle< Node > ) => { - const $selectedFilter = await $panel.$( - SELECTORS.customSelectControl.button - ); - if ( ! $selectedFilter ) { - throw new Error( 'Can not find selected filter.' ); - } - return await page.evaluate( ( el ) => el.textContent, $selectedFilter ); -}; - -const selectPopularFilter = async ( filter: string ) => { - let $panel = await getPopularFilterPanel(); - const $toggleButton = await $panel.$( - SELECTORS.customSelectControl.button - ); - await $toggleButton.click(); - await $panel.waitForSelector( - SELECTORS.customSelectControl.menu( { hidden: false } ) - ); - const [ $filter ] = await $panel.$x( - `//li[contains(text(), "${ filter }")]` - ); - if ( ! $filter ) { - throw new Error( - `Filter "${ filter }" not found among Popular Filters options` - ); - } - await $filter.click(); - /** - * We use try with empty catch block here to avoid the race condition - * between the block loading and the test execution. After user actions, - * the products may or may not finish loading at the time we try to wait for - * the loading class. - */ - try { - await canvas().waitForSelector( SELECTORS.productsGridLoading ); - } catch ( ok ) {} - await canvas().waitForSelector( SELECTORS.productsGrid ); - await saveOrPublish(); - - // Verify if filter is selected or try again. - await visitBlockPage( `${ block.name } Block` ); - $panel = await getPopularFilterPanel(); - const currentSelectedFilter = await getCurrentPopularFilter( $panel ); - if ( currentSelectedFilter !== filter ) { - await selectPopularFilter( filter ); - } -}; - -// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. -describe.skip( 'Product Query > Popular Filters', () => { - let $popularFiltersPanel: ElementHandle< Node >; - beforeEach( async () => { - /** - * Reset the block page before each test to ensure the block is - * inserted in a known state. This is also needed to ensure each - * test can be run individually. - */ - await resetProductQueryBlockPage(); - $popularFiltersPanel = await getPopularFilterPanel(); - } ); - - /** - * Reset the content of Product Query Block page after this test suite - * to avoid breaking other tests. - */ - afterAll( async () => { - await resetProductQueryBlockPage(); - } ); - - it( 'Popular Filters is expanded by default', async () => { - await expect( $popularFiltersPanel ).toMatch( - 'Arrange products by popular pre-sets.' - ); - } ); - - it( 'Sorted by title is the default preset', async () => { - const currentFilter = await getCurrentPopularFilter( - $popularFiltersPanel - ); - expect( currentFilter ).toEqual( 'Sorted by title' ); - } ); - - describe.each( [ - { - filter: 'Sorted by title', - shortcode: '[products orderby="title" order="ASC" limit="9"]', - }, - { - filter: 'Newest', - shortcode: '[products orderby="date" order="DESC" limit="9"]', - }, - /** - * The following tests are commented out because they are flaky - * due to the lack of orders and reviews in the test environment. - * - * @see https://github.com/woocommerce/woocommerce-blocks/issues/8116 - */ - // { - // filter: 'Best Selling', - // shortcode: '[products best_selling="true" limit="9"]', - // }, - // { - // filter: 'Top Rated', - // shortcode: '[products top_rated="true" limit="9"]', - // }, - ] )( '$filter', ( { filter, shortcode } ) => { - beforeEach( async () => { - await selectPopularFilter( filter ); - } ); - it( 'Editor preview and block frontend display the same products', async () => { - const { previewProducts, frontEndProducts } = - await setupEditorFrontendComparison(); - expect( frontEndProducts ).toEqual( previewProducts ); - } ); - - it( 'Products are displayed in the correct order', async () => { - const { productQueryProducts, shortcodeProducts } = - await setupProductQueryShortcodeComparison( shortcode ); - expect( productQueryProducts ).toEqual( shortcodeProducts ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-results-count.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-results-count.test.js deleted file mode 100644 index 5c4a7c125bec4..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-results-count.test.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * External dependencies - */ -import { - canvas, - createNewPost, - insertBlock, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { searchForBlock } from '@wordpress/e2e-test-utils/build/inserter'; - -/** - * Internal dependencies - */ -import { - filterCurrentBlocks, - goToSiteEditor, - useTheme, - waitForCanvas, -} from '../../utils.js'; - -const block = { - name: 'Product Results Count', - slug: 'woocommerce/product-results-count', - class: '.wc-block-product-results-count', -}; - -describe( `${ block.name } Block`, () => { - it( 'can not be inserted in a post', async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - await searchForBlock( block.name ); - expect( page ).toMatch( 'No results found.' ); - } ); - - describe.skip( 'in FSE editor', () => { - useTheme( 'emptytheme' ); - - beforeEach( async () => { - await goToSiteEditor(); - await waitForCanvas(); - } ); - - it( 'can be inserted in FSE area', async () => { - await insertBlock( block.name ); - await expect( canvas() ).toMatchElement( block.class ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlock( block.name ); - await insertBlock( block.name ); - const filteredBlocks = await filterCurrentBlocks( - ( b ) => b.name === block.slug - ); - expect( filteredBlocks ).toHaveLength( 2 ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-search-legacy.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-search-legacy.test.ts deleted file mode 100644 index 9609e79047abd..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-search-legacy.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * External dependencies - */ -import { switchUserToAdmin } from '@wordpress/e2e-test-utils'; -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. -describe.skip( 'Product Search Legacy Block', () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( 'Product Search Legacy Block' ); - } ); - - it( 'render the upgrade prompt', async () => { - await expect( page ).toMatch( - 'This version of the Product Search block is outdated. Upgrade to continue using.' - ); - await expect( page ).toMatch( 'Upgrade Block' ); - } ); - - it( 'clicking the upgrade button convert the legacy block to core/search variation', async () => { - await page.click( '.block-editor-warning__action button' ); - - await expect( page ).toMatchElement( '.wp-block-search' ); - - await expect( page ).toMatchElement( '.wp-block-search__label', { - text: 'Search', - } ); - - await expect( page ).toMatchElement( - '.wp-block-search__input[value="Search products…"]' - ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-search.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-search.test.ts deleted file mode 100644 index 2f081cdb83959..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-search.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * External dependencies - */ -import { - switchUserToAdmin, - createNewPost, - insertBlock, -} from '@wordpress/e2e-test-utils'; - -const block = { - name: 'Product Search', - slug: 'core/search', - class: '.wp-block-search', -}; - -// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. -describe.skip( `${ block.name } Block`, () => { - it( 'inserting Product Search block renders the core/search variation', async () => { - await switchUserToAdmin(); - - await createNewPost( { - postType: 'page', - } ); - - await insertBlock( block.name ); - - await page.waitForSelector( block.class ); - - await expect( page ).toRenderBlock( block ); - - await expect( page ).toMatchElement( '.wp-block-search__label', { - text: 'Search', - } ); - - await expect( page ).toMatchElement( - '.wp-block-search__input[value="Search products…"]' - ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-tag.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-tag.test.js deleted file mode 100644 index 4e9d58d6ca236..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-tag.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Products by Tag', - slug: 'woocommerce/product-tag', - class: '.wc-block-product-tag', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-top-rated.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-top-rated.test.js deleted file mode 100644 index cfb3fdab7513b..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/product-top-rated.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Top Rated Products', - slug: 'woocommerce/product-top-rated', - class: '.wc-block-product-top-rated', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 2 ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/products-by-attribute.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/products-by-attribute.test.js deleted file mode 100644 index 0204ef14d3484..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/products-by-attribute.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * External dependencies - */ -import { getAllBlocks, switchUserToAdmin } from '@wordpress/e2e-test-utils'; - -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { insertBlockDontWaitForInsertClose } from '../../utils.js'; - -const block = { - name: 'Products by Attribute', - slug: 'woocommerce/products-by-attribute', - class: '.wc-block-products-by-attribute', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - expect( await getAllBlocks() ).toHaveLength( 3 ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/rating-filter.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/rating-filter.test.js deleted file mode 100644 index 92202a1fcfd90..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/rating-filter.test.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * External dependencies - */ -import { - switchBlockInspectorTab, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { openSettingsSidebar } from '../../utils'; - -const block = { - name: 'Filter by Rating', - slug: 'woocommerce/rating-filter', - class: '.wc-block-rating-filter', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - describe( 'attributes', () => { - beforeEach( async () => { - await openSettingsSidebar(); - await page.click( block.class ); - await switchBlockInspectorTab( 'Settings' ); - } ); - - it( 'product count can be toggled', async () => { - await expect( page ).not.toMatchElement( - '.wc-block-components-product-rating-count' - ); - await expect( page ).toClick( 'label', { - text: 'Display product count', - } ); - await expect( page ).toMatchElement( - '.wc-block-components-product-rating-count' - ); - // reset - await expect( page ).toClick( 'label', { - text: 'Display product count', - } ); - } ); - - it( 'filter button can be toggled', async () => { - await expect( page ).not.toMatchElement( - 'button.wc-block-filter-submit-button.wc-block-rating-filter__button' - ); - await expect( page ).toClick( 'label', { - text: "Show 'Apply filters' button", - } ); - await expect( page ).toMatchElement( - 'button.wc-block-filter-submit-button.wc-block-rating-filter__button' - ); - // reset - await expect( page ).toClick( 'label', { - text: "Show 'Apply filters' button", - } ); - } ); - - it( 'allows changing the Display Style', async () => { - // Turn the display style to Dropdown - await expect( page ).toClick( 'button', { text: 'Dropdown' } ); - - await page.waitForSelector( - '.wc-block-rating-filter.style-dropdown' - ); - await expect( page ).toMatchElement( - '.wc-block-rating-filter.style-dropdown' - ); - // Turn the display style to List - await expect( page ).toClick( 'button', { - text: 'List', - } ); - - await page.waitForSelector( '.wc-block-rating-filter.style-list' ); - await expect( page ).toMatchElement( - '.wc-block-rating-filter.style-list' - ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/single-product-details.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/single-product-details.test.js deleted file mode 100644 index 17344b50b12ef..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/single-product-details.test.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * External dependencies - */ -import { - canvas, - createNewPost, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { searchForBlock } from '@wordpress/e2e-test-utils/build/inserter'; - -/** - * Internal dependencies - */ -import { - filterCurrentBlocks, - goToTemplateEditor, - insertBlockDontWaitForInsertClose, - useTheme, -} from '../../utils.js'; - -const block = { - name: 'Product Details', - slug: 'woocommerce/product-details', - class: '.wp-block-woocommerce-product-details', -}; - -describe( `${ block.name } Block`, () => { - it( 'can not be inserted in a post', async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - await searchForBlock( block.name ); - expect( page ).toMatch( 'No results found.' ); - } ); - - describe.skip( 'in FSE editor', () => { - useTheme( 'emptytheme' ); - - beforeEach( async () => { - await goToTemplateEditor( { - postId: 'woocommerce/woocommerce//single-product', - } ); - } ); - - it( 'can be inserted in FSE area', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - await expect( canvas() ).toMatchElement( block.class ); - } ); - - it( 'can be inserted more than once', async () => { - await insertBlockDontWaitForInsertClose( block.name ); - const filteredBlocks = await filterCurrentBlocks( - ( b ) => b.name === block.slug - ); - expect( filteredBlocks ).toHaveLength( 2 ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/stock-filter.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/stock-filter.test.js deleted file mode 100644 index 626899eeb4376..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/stock-filter.test.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * External dependencies - */ -import { - switchBlockInspectorTab, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { visitBlockPage } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -/** - * Internal dependencies - */ -import { openSettingsSidebar } from '../../utils'; -import { findLabelWithText } from '../../../utils'; - -const block = { - name: 'Filter Products by Stock', - slug: 'woocommerce/stock-filter', - class: '.wc-block-stock-filter', -}; - -describe( `${ block.name } Block`, () => { - beforeAll( async () => { - await switchUserToAdmin(); - await visitBlockPage( `${ block.name } Block` ); - } ); - - it( 'renders without crashing', async () => { - await expect( page ).toRenderBlock( block ); - } ); - - describe( 'attributes', () => { - beforeEach( async () => { - await openSettingsSidebar(); - await page.click( block.class ); - await switchBlockInspectorTab( 'Settings' ); - } ); - - it( 'product count can be toggled', async () => { - const toggleLabel = await findLabelWithText( - 'Display product count' - ); - await expect( toggleLabel ).toToggleElement( - `${ block.class } .wc-filter-element-label-list-count` - ); - } ); - - it( 'filter button can be toggled', async () => { - const toggleLabel = await findLabelWithText( 'Apply filters' ); - await expect( toggleLabel ).toToggleElement( - `${ block.class } .wc-block-filter-submit-button` - ); - } ); - - it( 'allows changing the Display Style', async () => { - // Turn the display style to Dropdown - await expect( page ).toClick( 'button', { text: 'Dropdown' } ); - - await expect( page ).toMatchElement( - '.wc-block-stock-filter.style-dropdown' - ); - // Turn the display style to List - await expect( page ).toClick( 'button', { - text: 'List', - } ); - - await expect( page ).toMatchElement( - '.wc-block-stock-filter.style-list' - ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/store-notices.test.js b/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/store-notices.test.js deleted file mode 100644 index c5078667df833..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/backend/store-notices.test.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * External dependencies - */ -import { - canvas, - createNewPost, - insertBlock, - switchUserToAdmin, -} from '@wordpress/e2e-test-utils'; -import { searchForBlock } from '@wordpress/e2e-test-utils/build/inserter'; - -/** - * Internal dependencies - */ -import { goToSiteEditor, useTheme, waitForCanvas } from '../../utils.js'; - -const block = { - name: 'Store Notices', - slug: 'woocommerce/store-notices', - class: '.wc-block-store-notices', - selectors: { - insertButton: "//button//span[text()='Store Notices']", - insertButtonDisabled: - "//button[@aria-disabled]//span[text()='Store Notices']", - }, -}; - -describe( `${ block.name } Block`, () => { - it( 'can not be inserted in the Post Editor', async () => { - await switchUserToAdmin(); - - await createNewPost( { - postType: 'post', - title: block.name, - } ); - await searchForBlock( block.name ); - expect( page ).toMatch( 'No results found.' ); - } ); - - describe.skip( 'in FSE editor', () => { - useTheme( 'emptytheme' ); - - beforeEach( async () => { - await goToSiteEditor(); - await waitForCanvas(); - } ); - - it( 'can be inserted in FSE area', async () => { - await insertBlock( block.name ); - await expect( canvas() ).toMatchElement( block.class ); - } ); - - it( 'can only be inserted once', async () => { - await insertBlock( block.name ); - await searchForBlock( block.name ); - const storeNoticesButton = await page.$x( - block.selectors.insertButtonDisabled - ); - expect( storeNoticesButton ).toHaveLength( 1 ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/active-filters.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/active-filters.test.ts deleted file mode 100644 index 0b23f8652cbc3..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/active-filters.test.ts +++ /dev/null @@ -1,362 +0,0 @@ -/** - * External dependencies - */ -import { - insertBlock, - deleteAllTemplates, - canvas, - createNewPost, - switchUserToAdmin, - publishPost, -} from '@wordpress/e2e-test-utils'; -import { SHOP_PAGE } from '@woocommerce/e2e-utils'; -import { Frame, Page } from 'puppeteer'; - -/** - * Internal dependencies - */ -import { - goToTemplateEditor, - insertAllProductsBlock, - useTheme, - saveTemplate, - waitForAllProductsBlockLoaded, -} from '../../utils'; -import { - clickLink, - shopper, - SIMPLE_PHYSICAL_PRODUCT_NAME, -} from '../../../utils'; - -const block = { - name: 'Active Filters', - slug: 'woocommerce/active-filters', - class: '.wp-block-woocommerce-active-filters', - selectors: { - editor: { - firstAttributeInTheList: - '.woocommerce-search-list__list > li > label > input.woocommerce-search-list__item-input', - filterButtonToggle: "//label[text()='Filter button']", - doneButton: '.wc-block-attribute-filter__selection > button', - }, - frontend: { - activeFilterType: '.wc-block-active-filters__list-item-type', - activeFilterName: '.wc-block-active-filters__list-item-name', - removeFilterButton: '.wc-block-active-filters__list-item-remove', - removeAllFiltersButton: '.wc-block-active-filters__clear-all', - stockFilterBlock: '.wc-block-stock-filter-list', - attributeFilterBlock: '.wc-block-attribute-filter-list', - productsList: '.wc-block-grid__products > li', - productsBlockProducts: '.wp-block-post-template > li', - classicProductsList: '.products.columns-3 > li', - }, - }, -}; - -const FILTER_STOCK_STATUS_TITLE = 'Stock Status'; -const FILTER_STOCK_STATUS_PROPERTY = 'In stock'; -const FILTER_CAPACITY_TITLE = 'Capacity:'; -const FILTER_CAPACITY_PROPERTY = '128gb'; - -const { selectors } = block; - -const insertBlocks = async () => { - await insertBlock( 'Filter by Price' ); - await insertBlock( 'Filter by Stock' ); - await insertBlock( 'Filter by Attribute' ); - await insertBlock( block.name ); -}; - -const configureAttributeFilterBlock = async ( pageOrCanvas: Page | Frame ) => { - await pageOrCanvas.$eval( - selectors.editor.firstAttributeInTheList, - ( el ) => ( el as HTMLElement ).click() - ); - await pageOrCanvas.click( selectors.editor.doneButton ); -}; - -const getActiveFilterTypeText = () => - page.$eval( - selectors.frontend.activeFilterType, - ( el ) => ( el as HTMLElement ).innerText - ); - -const getActiveFilterNameText = () => - page.$eval( - selectors.frontend.activeFilterName, - ( el ) => ( el as HTMLElement ).childNodes[ 1 ].textContent - ); - -describe.skip( 'Shopper → Active Filters Block', () => { - describe( 'With All Products block', () => { - beforeAll( async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - - await insertBlocks(); - await insertAllProductsBlock(); - await configureAttributeFilterBlock( page ); - await publishPost(); - - const link = await page.evaluate( () => - wp.data.select( 'core/editor' ).getPermalink() - ); - await page.goto( link ); - } ); - - beforeEach( async () => { - await page.reload(); - } ); - - it( 'Active Filters is hidden if there is no filter selected', async () => { - expect( page ).not.toMatch( 'Active Filters' ); - } ); - - it.skip( 'Shows selected filters', async () => { - const isRefreshed = jest.fn( () => void 0 ); - - await page.waitForSelector( block.class ); - await page.waitForSelector( - selectors.frontend.attributeFilterBlock + '.is-loading', - { hidden: true } - ); - - await page.waitForSelector( selectors.frontend.stockFilterBlock ); - - await expect( page ).toClick( 'label', { - text: FILTER_CAPACITY_PROPERTY, - } ); - - const activeFilterType = await getActiveFilterTypeText(); - - expect( activeFilterType ).toBe( - FILTER_CAPACITY_TITLE.toUpperCase() - ); - - await waitForAllProductsBlockLoaded(); - - await expect( page ).toClick( 'label', { - text: FILTER_STOCK_STATUS_PROPERTY, - } ); - - await expect( page ).toMatch( FILTER_STOCK_STATUS_TITLE ); - - const activeFilterNameText = await getActiveFilterNameText(); - - expect( activeFilterNameText ).toBe( FILTER_STOCK_STATUS_PROPERTY ); - - await waitForAllProductsBlockLoaded(); - - const products = await page.$$( selectors.frontend.productsList ); - expect( products ).toHaveLength( 1 ); - expect( isRefreshed ).not.toHaveBeenCalled(); - await expect( page ).toMatch( SIMPLE_PHYSICAL_PRODUCT_NAME ); - } ); - - it.skip( 'When clicking the X on a filter it removes a filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - await page.waitForSelector( block.class ); - await page.waitForSelector( - selectors.frontend.attributeFilterBlock + '.is-loading', - { - hidden: true, - } - ); - - await expect( page ).toClick( 'label', { - text: FILTER_CAPACITY_PROPERTY, - } ); - - await expect( page ).toClick( - selectors.frontend.removeFilterButton - ); - - expect( page ).not.toMatch( 'Active Filters' ); - - await waitForAllProductsBlockLoaded(); - - const products = await page.$$( selectors.frontend.productsList ); - expect( products ).toHaveLength( 5 ); - expect( isRefreshed ).not.toHaveBeenCalled(); - } ); - - it.skip( 'Clicking "Clear All" button removes all active filters', async () => { - const isRefreshed = jest.fn( () => void 0 ); - await page.waitForSelector( block.class ); - await page.waitForSelector( - selectors.frontend.attributeFilterBlock + '.is-loading', - { hidden: true } - ); - await page.waitForSelector( selectors.frontend.stockFilterBlock ); - - await expect( page ).toClick( 'label', { - text: FILTER_STOCK_STATUS_PROPERTY, - } ); - - await expect( page ).toClick( 'label', { - text: FILTER_CAPACITY_PROPERTY, - } ); - - await page.click( selectors.frontend.removeAllFiltersButton ); - - await waitForAllProductsBlockLoaded(); - - const products = await page.$$( selectors.frontend.productsList ); - - expect( products ).toHaveLength( 5 ); - expect( isRefreshed ).not.toHaveBeenCalled(); - } ); - } ); - describe.skip( 'With PHP Templates (Products Block and Classic Template Block)', () => { - useTheme( 'emptytheme' ); - beforeAll( async () => { - await deleteAllTemplates( 'wp_template_part' ); - await deleteAllTemplates( 'wp_template' ); - await goToTemplateEditor( { - postId: 'woocommerce/woocommerce//archive-product', - } ); - - await insertBlock( 'WooCommerce Product Grid Block' ); - await insertBlocks(); - - const canvasEl = canvas(); - await configureAttributeFilterBlock( canvasEl ); - await saveTemplate(); - } ); - - beforeEach( async () => { - await shopper.goToShop(); - } ); - - afterAll( async () => { - await deleteAllTemplates( 'wp_template' ); - await deleteAllTemplates( 'wp_template_part' ); - } ); - - it( 'Active Filters is hidden if there is no filter selected', async () => { - expect( page ).not.toMatch( 'Active Filters' ); - } ); - - it( 'Shows selected filters', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - await page.waitForSelector( block.class ); - await page.waitForSelector( - selectors.frontend.attributeFilterBlock + '.is-loading', - { hidden: true } - ); - - await expect( page ).toClick( 'label', { - text: FILTER_CAPACITY_PROPERTY, - } ); - - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - - await page.waitForSelector( block.class ); - - const activeFilterType = await getActiveFilterTypeText(); - - expect( activeFilterType ).toBe( - FILTER_CAPACITY_TITLE.toUpperCase() - ); - await page.waitForSelector( selectors.frontend.stockFilterBlock ); - - await expect( page ).toClick( 'label', { - text: FILTER_STOCK_STATUS_PROPERTY, - } ); - - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - - await page.waitForSelector( block.class ); - - const activeFilterNameText = await getActiveFilterNameText(); - expect( activeFilterNameText ).toBe( FILTER_STOCK_STATUS_PROPERTY ); - - const classicProductsList = await page.$$( - selectors.frontend.classicProductsList - ); - - const products = await page.$$( - selectors.frontend.productsBlockProducts - ); - - expect( isRefreshed ).toHaveBeenCalledTimes( 2 ); - expect( products ).toHaveLength( 1 ); - expect( classicProductsList ).toHaveLength( 1 ); - await expect( page ).toMatch( SIMPLE_PHYSICAL_PRODUCT_NAME ); - } ); - - it( 'When clicking the X on a filter it removes a filter and triggers a page refresh', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - await page.waitForSelector( selectors.frontend.stockFilterBlock ); - - expect( isRefreshed ).not.toHaveBeenCalled(); - await expect( page ).toClick( 'label', { - text: FILTER_STOCK_STATUS_PROPERTY, - } ); - - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - - await page.waitForSelector( block.class ); - - await expect( page ).toClick( 'label', { - text: FILTER_CAPACITY_PROPERTY, - } ); - - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - - await page.waitForSelector( block.class ); - - await clickLink( selectors.frontend.removeFilterButton ); - - const classicProductsList = await page.$$( - selectors.frontend.classicProductsList - ); - - const products = await page.$$( - selectors.frontend.productsBlockProducts - ); - - expect( page.url() ).not.toMatch( 'instock' ); - expect( page.url() ).toMatch( FILTER_CAPACITY_PROPERTY ); - expect( isRefreshed ).toHaveBeenCalledTimes( 3 ); - expect( products ).toHaveLength( 1 ); - expect( classicProductsList ).toHaveLength( 1 ); - await expect( page ).toMatch( SIMPLE_PHYSICAL_PRODUCT_NAME ); - } ); - - it( 'Clicking "Clear All" button removes all active filters and the page redirects to the base URL', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - await page.waitForSelector( selectors.frontend.stockFilterBlock ); - - await expect( page ).toClick( 'label', { - text: FILTER_STOCK_STATUS_PROPERTY, - } ); - - await page.waitForNavigation( { waitUntil: 'networkidle0' } ); - - await page.waitForSelector( block.class ); - - await clickLink( selectors.frontend.removeAllFiltersButton ); - - const classicProductsList = await page.$$( - selectors.frontend.classicProductsList - ); - - const products = await page.$$( - selectors.frontend.productsBlockProducts - ); - - expect( page.url() ).not.toMatch( 'instock' ); - expect( page.url() ).toMatch( SHOP_PAGE ); - expect( isRefreshed ).toHaveBeenCalledTimes( 2 ); - expect( classicProductsList ).toHaveLength( 5 ); - expect( products ).toHaveLength( 5 ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-attribute.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-attribute.test.ts deleted file mode 100644 index e5704feed78c3..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-attribute.test.ts +++ /dev/null @@ -1,343 +0,0 @@ -/** - * External dependencies - */ -import { - canvas, - createNewPost, - deleteAllTemplates, - insertBlock, - switchUserToAdmin, - publishPost, -} from '@wordpress/e2e-test-utils'; -import { selectBlockByName } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { - BASE_URL, - enableApplyFiltersButton, - goToTemplateEditor, - insertAllProductsBlock, - saveTemplate, - useTheme, - waitForAllProductsBlockLoaded, - waitForCanvas, -} from '../../utils'; -import { saveOrPublish } from '../../../utils'; - -const block = { - name: 'Filter by Attribute', - slug: 'woocommerce/attribute-filter', - class: '.wc-block-attribute-filter', - selectors: { - editor: { - firstAttributeInTheList: - '.woocommerce-search-list__list > li > label > input.woocommerce-search-list__item-input', - doneButton: '.wc-block-attribute-filter__selection > button', - }, - frontend: { - firstAttributeInTheList: - '.wc-block-attribute-filter-list > li:not([class^="is-loading"])', - productsList: '.wc-block-grid__products > li', - queryProductsList: '.wp-block-post-template > li', - classicProductsList: '.products.columns-3 > li', - filter: "input[id='128gb']", - submitButton: '.wc-block-components-filter-submit-button', - }, - }, - urlSearchParamWhenFilterIsApplied: - '?filter_capacity=128gb&query_type_capacity=or', - foundProduct: '128GB USB Stick', -}; - -const { selectors } = block; - -const goToShopPage = () => - page.goto( BASE_URL + '/shop', { - waitUntil: 'networkidle0', - } ); - -describe( `${ block.name } Block`, () => { - const insertFilterByAttributeBlock = async () => { - await insertBlock( block.name ); - const canvasEl = canvas(); - - // It seems that .click doesn't work well with radio input element. - await canvasEl.$eval( - block.selectors.editor.firstAttributeInTheList, - ( el ) => ( el as HTMLInputElement ).click() - ); - await canvasEl.click( selectors.editor.doneButton ); - }; - - describe( 'with All Products Block', () => { - beforeAll( async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - - await insertAllProductsBlock(); - await insertFilterByAttributeBlock(); - await publishPost(); - - const link = await page.evaluate( () => - wp.data.select( 'core/editor' ).getPermalink() - ); - - await page.goto( link ); - } ); - - it.skip( 'should render products', async () => { - await waitForAllProductsBlockLoaded(); - const products = await page.$$( selectors.frontend.productsList ); - - expect( products ).toHaveLength( 5 ); - } ); - - it.skip( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( selectors.frontend.filter ); - await page.click( selectors.frontend.filter ); - await waitForAllProductsBlockLoaded(); - const products = await page.$$( selectors.frontend.productsList ); - - expect( isRefreshed ).not.toBeCalled(); - expect( products ).toHaveLength( 1 ); - await expect( page ).toMatch( block.foundProduct ); - } ); - } ); - - describe.skip( 'with PHP classic template (Products Block and Classic Template Block)', () => { - const productCatalogTemplateId = - 'woocommerce/woocommerce//archive-product'; - - useTheme( 'emptytheme' ); - beforeAll( async () => { - await deleteAllTemplates( 'wp_template' ); - await deleteAllTemplates( 'wp_template_part' ); - - await goToTemplateEditor( { - postId: productCatalogTemplateId, - } ); - await insertBlock( 'WooCommerce Product Grid Block' ); - await insertFilterByAttributeBlock(); - await saveTemplate(); - } ); - - afterAll( async () => { - await deleteAllTemplates( 'wp_template' ); - await deleteAllTemplates( 'wp_template_part' ); - } ); - - beforeEach( async () => { - await goToShopPage(); - } ); - - it( 'should render products', async () => { - const products = await page.$$( - selectors.frontend.classicProductsList - ); - - const productsBlockProductsList = await page.$$( - selectors.frontend.queryProductsList - ); - - expect( productsBlockProductsList ).toHaveLength( 5 ); - expect( products ).toHaveLength( 5 ); - } ); - - it( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.filter ); - - await Promise.all( [ - page.click( selectors.frontend.filter ), - page.waitForNavigation( { waitUntil: 'networkidle0' } ), - ] ); - - const products = await page.$$( - selectors.frontend.classicProductsList - ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - const productsBlockProductsList = await page.$$( - selectors.frontend.queryProductsList - ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - expect( productsBlockProductsList ).toHaveLength( 1 ); - await expect( page ).toMatch( block.foundProduct ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - - it.skip( 'should refresh the page only if the user clicks on button', async () => { - await goToTemplateEditor( { - postId: productCatalogTemplateId, - } ); - - await waitForCanvas(); - await selectBlockByName( block.slug ); - await enableApplyFiltersButton(); - await saveTemplate(); - await goToShopPage(); - - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - await page.waitForSelector( selectors.frontend.filter ); - await page.click( selectors.frontend.filter ); - - expect( isRefreshed ).not.toBeCalled(); - - await Promise.all( [ - page.waitForNavigation( { - waitUntil: 'networkidle0', - } ), - page.click( selectors.frontend.submitButton ), - ] ); - - const products = await page.$$( - selectors.frontend.classicProductsList - ); - - const productsBlockProductsList = await page.$$( - selectors.frontend.queryProductsList - ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - expect( productsBlockProductsList ).toHaveLength( 1 ); - await expect( page ).toMatch( block.foundProduct ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - } ); - - describe( 'with Product Query Block', () => { - let editorPageUrl = ''; - let frontedPageUrl = ''; - - useTheme( 'emptytheme' ); - beforeAll( async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - - await insertBlock( 'Products (Beta)' ); - await insertFilterByAttributeBlock(); - await publishPost(); - - editorPageUrl = page.url(); - frontedPageUrl = await page.evaluate( () => - wp.data.select( 'core/editor' ).getPermalink() - ); - await page.goto( frontedPageUrl, { waitUntil: 'networkidle2' } ); - } ); - - it.skip( 'should render products', async () => { - const products = await page.$$( - selectors.frontend.queryProductsList - ); - - expect( products ).toHaveLength( 5 ); - } ); - - it( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.filter ); - - await Promise.all( [ - page.click( selectors.frontend.filter ), - page.waitForNavigation( { waitUntil: 'networkidle0' } ), - ] ); - - const products = await page.$$( - selectors.frontend.queryProductsList - ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - await expect( page ).toMatch( block.foundProduct ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - - it( 'should refresh the page only if the user clicks on button', async () => { - await page.goto( editorPageUrl ); - - await waitForCanvas(); - await selectBlockByName( block.slug ); - await enableApplyFiltersButton(); - await saveOrPublish(); - await page.goto( frontedPageUrl ); - - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - await page.waitForSelector( selectors.frontend.filter ); - await page.click( selectors.frontend.filter ); - - expect( isRefreshed ).not.toBeCalled(); - - await Promise.all( [ - page.waitForNavigation( { - waitUntil: 'networkidle0', - } ), - page.click( selectors.frontend.submitButton ), - ] ); - - const products = await page.$$( - selectors.frontend.queryProductsList - ); - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - await expect( page ).toMatch( block.foundProduct ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-price.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-price.test.ts deleted file mode 100644 index a28bc5cabc097..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-price.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * External dependencies - */ -import { - createNewPost, - insertBlock, - switchUserToAdmin, - publishPost, -} from '@wordpress/e2e-test-utils'; -import { selectBlockByName } from '@woocommerce/blocks-test-utils'; - -/** - * Internal dependencies - */ -import { enableApplyFiltersButton, waitForCanvas } from '../../utils'; -import { clickLink, saveOrPublish } from '../../../utils'; - -const block = { - name: 'Filter by Price', - slug: 'woocommerce/price-filter', - class: '.wc-block-price-filter', - selectors: { - frontend: { - priceMaxAmount: '.wc-block-price-filter__amount--max', - productsList: '.wc-block-grid__products > li', - queryProductsList: '.wp-block-post-template > li', - classicProductsList: '.products.columns-3 > li', - submitButton: '.wc-block-components-filter-submit-button', - }, - }, - urlSearchParamWhenFilterIsApplied: '?max_price=2', - foundProduct: '32GB USB Stick', -}; - -const { selectors } = block; - -const setMaxPrice = async () => { - await page.waitForSelector( selectors.frontend.priceMaxAmount ); - await page.focus( selectors.frontend.priceMaxAmount ); - await page.keyboard.down( 'Shift' ); - await page.keyboard.press( 'Home' ); - await page.keyboard.up( 'Shift' ); - await page.keyboard.type( '2' ); - await page.keyboard.press( 'Tab' ); -}; - -describe.skip( `${ block.name } Block`, () => { - describe( 'with Product Query Block', () => { - let editorPageUrl = ''; - let frontedPageUrl = ''; - beforeAll( async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - - await insertBlock( 'Products (Beta)' ); - await insertBlock( block.name ); - await insertBlock( 'Active Filters' ); - await page.waitForNetworkIdle(); - await publishPost(); - - editorPageUrl = page.url(); - frontedPageUrl = await page.evaluate( () => - wp.data.select( 'core/editor' ).getPermalink() - ); - await page.goto( frontedPageUrl ); - } ); - - it( 'should render products', async () => { - const products = await page.$$( - selectors.frontend.queryProductsList - ); - - expect( products ).toHaveLength( 5 ); - } ); - - it( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - await expect( page ).toMatch( block.foundProduct ); - expect( isRefreshed ).not.toBeCalled(); - - await Promise.all( [ setMaxPrice(), page.waitForNavigation() ] ); - - await page.waitForSelector( selectors.frontend.queryProductsList ); - const products = await page.$$( - selectors.frontend.queryProductsList - ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - await expect( page ).toMatch( block.foundProduct ); - } ); - - it( 'should refresh the page only if the user click on button', async () => { - await page.goto( editorPageUrl ); - - await waitForCanvas(); - await selectBlockByName( block.slug ); - await enableApplyFiltersButton(); - await saveOrPublish(); - await page.goto( frontedPageUrl ); - - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await setMaxPrice(); - - expect( isRefreshed ).not.toBeCalled(); - - await clickLink( selectors.frontend.submitButton ); - - await page.waitForSelector( selectors.frontend.queryProductsList ); - - const products = await page.$$( - selectors.frontend.queryProductsList - ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - await expect( page ).toMatch( block.foundProduct ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-rating.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-rating.test.ts deleted file mode 100644 index 7c0768b625fcc..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-rating.test.ts +++ /dev/null @@ -1,318 +0,0 @@ -/** - * External dependencies - */ -import { - createNewPost, - deleteAllTemplates, - insertBlock, - switchBlockInspectorTab, - switchUserToAdmin, - publishPost, - ensureSidebarOpened, -} from '@wordpress/e2e-test-utils'; -import { - selectBlockByName, - saveOrPublish, - getToggleIdByLabel, -} from '@woocommerce/blocks-test-utils'; -import { setCheckbox } from '@woocommerce/e2e-utils'; - -/** - * Internal dependencies - */ -import { - BASE_URL, - enableApplyFiltersButton, - goToTemplateEditor, - insertAllProductsBlock, - saveTemplate, - useTheme, - waitForAllProductsBlockLoaded, - waitForCanvas, -} from '../../utils'; - -const block = { - name: 'Filter by Rating', - slug: 'woocommerce/rating-filter', - class: '.wc-block-rating-filter', - selectors: { - frontend: { - productsList: '.wc-block-grid__products > li', - queryProductsList: '.wp-block-post-template > li', - classicProductsList: '.products.columns-3 > li', - fiveStarInput: ".wc-block-rating-filter label[for='5'] input", - submitButton: '.wc-block-components-filter-submit-button', - }, - }, - urlSearchParamWhenFilterIsApplied: '?rating_filter=5', -}; - -const FIVE_STAR_PRODUCTS_AMOUNT = 5; - -const { selectors } = block; - -const goToShopPage = () => - page.goto( BASE_URL + '/shop', { - waitUntil: 'networkidle0', - } ); - -describe( `${ block.name } Block`, () => { - describe.skip( 'with All Products Block', () => { - let link = ''; - beforeAll( async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - - await insertBlock( block.name ); - await insertAllProductsBlock(); - await publishPost(); - - link = await page.evaluate( () => - wp.data.select( 'core/editor' ).getPermalink() - ); - await page.goto( link ); - } ); - - it( 'should render', async () => { - await expect( page ).toMatchElement( block.class ); - } ); - - it( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - await waitForAllProductsBlockLoaded(); - await page.waitForSelector( selectors.frontend.fiveStarInput ); - await page.click( selectors.frontend.fiveStarInput ); - await waitForAllProductsBlockLoaded(); - const products = await page.$$( selectors.frontend.productsList ); - expect( isRefreshed ).not.toBeCalled(); - expect( products ).toHaveLength( FIVE_STAR_PRODUCTS_AMOUNT ); - } ); - } ); - - describe.skip( 'with PHP classic template (Products Block and Classic Template Block)', () => { - const productCatalogTemplateId = - 'woocommerce/woocommerce//archive-product'; - - useTheme( 'emptytheme' ); - beforeAll( async () => { - await deleteAllTemplates( 'wp_template' ); - await deleteAllTemplates( 'wp_template_part' ); - await goToTemplateEditor( { - postId: productCatalogTemplateId, - } ); - await insertBlock( 'WooCommerce Product Grid Block' ); - await insertBlock( block.name ); - await saveTemplate(); - await goToShopPage(); - } ); - - beforeEach( async () => { - await goToShopPage(); - } ); - - afterAll( async () => { - await deleteAllTemplates( 'wp_template' ); - await deleteAllTemplates( 'wp_template_part' ); - } ); - - it( 'should render', async () => { - await expect( page ).toMatchElement( block.class ); - } ); - - it( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.fiveStarInput ); - - await Promise.all( [ - page.waitForNavigation(), - page.click( selectors.frontend.fiveStarInput ), - ] ); - - const products = await page.$$( - selectors.frontend.classicProductsList - ); - - const productsBlockProductsList = await page.$$( - selectors.frontend.queryProductsList - ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( FIVE_STAR_PRODUCTS_AMOUNT ); - expect( productsBlockProductsList ).toHaveLength( - FIVE_STAR_PRODUCTS_AMOUNT - ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - - it( 'should refresh the page only if the user click on button', async () => { - await goToTemplateEditor( { - postId: productCatalogTemplateId, - } ); - - await waitForCanvas(); - await selectBlockByName( block.slug ); - await enableApplyFiltersButton(); - await saveTemplate(); - await goToShopPage(); - - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.fiveStarInput ); - await page.click( selectors.frontend.fiveStarInput ); - await Promise.all( [ - page.waitForNavigation( { - waitUntil: 'networkidle0', - } ), - page.click( selectors.frontend.submitButton ), - ] ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - await page.waitForSelector( - selectors.frontend.classicProductsList - ); - const products = await page.$$( - selectors.frontend.classicProductsList - ); - - const productsBlockProductsList = await page.$$( - selectors.frontend.queryProductsList - ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( FIVE_STAR_PRODUCTS_AMOUNT ); - expect( productsBlockProductsList ).toHaveLength( - FIVE_STAR_PRODUCTS_AMOUNT - ); - - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - } ); - - describe( 'with Product Query Block', () => { - let editorPageUrl = ''; - let frontedPageUrl = ''; - - useTheme( 'emptytheme' ); - beforeAll( async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - - await insertBlock( 'Products (Beta)' ); - await insertBlock( block.name ); - await publishPost(); - - editorPageUrl = page.url(); - frontedPageUrl = await page.evaluate( () => - wp.data.select( 'core/editor' ).getPermalink() - ); - await page.goto( frontedPageUrl ); - } ); - - it( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.fiveStarInput ); - - await Promise.all( [ - page.waitForNavigation(), - page.click( selectors.frontend.fiveStarInput ), - ] ); - - const products = await page.$$( - selectors.frontend.queryProductsList - ); - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( FIVE_STAR_PRODUCTS_AMOUNT ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - - it( 'should refresh the page only if the user click on button', async () => { - await page.goto( editorPageUrl ); - await waitForCanvas(); - await ensureSidebarOpened(); - await selectBlockByName( block.slug ); - await switchBlockInspectorTab( 'Settings' ); - await setCheckbox( - await getToggleIdByLabel( "Show 'Apply filters' button" ) - ); - - await saveOrPublish(); - await page.goto( frontedPageUrl ); - - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.fiveStarInput ); - await page.click( selectors.frontend.fiveStarInput ); - await Promise.all( [ - page.waitForNavigation( { - waitUntil: 'networkidle0', - } ), - page.click( selectors.frontend.submitButton ), - ] ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - await page.waitForSelector( selectors.frontend.queryProductsList ); - const products = await page.$$( - selectors.frontend.queryProductsList - ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( FIVE_STAR_PRODUCTS_AMOUNT ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-stock.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-stock.test.ts deleted file mode 100644 index 2278a38a5eed8..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/filter-products-by-stock.test.ts +++ /dev/null @@ -1,326 +0,0 @@ -/** - * External dependencies - */ -import { - createNewPost, - deleteAllTemplates, - insertBlock, - switchBlockInspectorTab, - switchUserToAdmin, - publishPost, - ensureSidebarOpened, -} from '@wordpress/e2e-test-utils'; -import { - selectBlockByName, - getToggleIdByLabel, - saveOrPublish, -} from '@woocommerce/blocks-test-utils'; -import { setCheckbox } from '@woocommerce/e2e-utils'; - -/** - * Internal dependencies - */ -import { - BASE_URL, - enableApplyFiltersButton, - goToTemplateEditor, - insertAllProductsBlock, - saveTemplate, - useTheme, - waitForAllProductsBlockLoaded, - waitForCanvas, -} from '../../utils'; - -const block = { - name: 'Filter by Stock', - slug: 'woocommerce/stock-filter', - class: '.wc-block-stock-filter', - selectors: { - frontend: { - productsList: '.wc-block-grid__products > li', - classicProductsList: '.products.columns-3 > li', - filter: 'input[id=outofstock]', - submitButton: '.wc-block-components-filter-submit-button', - queryProductsList: '.wp-block-post-template > li', - }, - }, - urlSearchParamWhenFilterIsApplied: '?filter_stock_status=outofstock', - foundProduct: 'Woo Single #3', -}; - -const { selectors } = block; - -const goToShopPage = () => - page.goto( BASE_URL + '/shop', { - waitUntil: 'networkidle0', - } ); - -describe( `${ block.name } Block`, () => { - describe.skip( 'with All Products Block', () => { - let link = ''; - beforeAll( async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - - await insertBlock( block.name ); - await insertAllProductsBlock(); - await publishPost(); - - link = await page.evaluate( () => - wp.data.select( 'core/editor' ).getPermalink() - ); - } ); - - it( 'should render', async () => { - await page.goto( link ); - await waitForAllProductsBlockLoaded(); - const products = await page.$$( selectors.frontend.productsList ); - - expect( products ).toHaveLength( 5 ); - } ); - - it( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - await page.click( selectors.frontend.filter ); - await waitForAllProductsBlockLoaded(); - const products = await page.$$( selectors.frontend.productsList ); - expect( isRefreshed ).not.toBeCalled(); - expect( products ).toHaveLength( 1 ); - await expect( page ).toMatch( block.foundProduct ); - } ); - } ); - - describe.skip( 'with PHP classic template (Products Block and Classic Template Block)', () => { - const productCatalogTemplateId = - 'woocommerce/woocommerce//archive-product'; - - useTheme( 'emptytheme' ); - beforeAll( async () => { - await deleteAllTemplates( 'wp_template' ); - await deleteAllTemplates( 'wp_template_part' ); - await goToTemplateEditor( { - postId: productCatalogTemplateId, - } ); - await insertBlock( 'WooCommerce Product Grid Block' ); - await insertBlock( block.name ); - await saveTemplate(); - await goToShopPage(); - } ); - - beforeEach( async () => { - await goToShopPage(); - } ); - - afterAll( async () => { - await deleteAllTemplates( 'wp_template' ); - await deleteAllTemplates( 'wp_template_part' ); - } ); - - it( 'should render', async () => { - const products = await page.$$( - selectors.frontend.classicProductsList - ); - - const productsBlockProductsList = await page.$$( - selectors.frontend.queryProductsList - ); - - expect( products ).toHaveLength( 5 ); - expect( productsBlockProductsList ).toHaveLength( 5 ); - } ); - - it( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.filter ); - - await Promise.all( [ - page.waitForNavigation(), - page.click( selectors.frontend.filter ), - ] ); - - const products = await page.$$( - selectors.frontend.classicProductsList - ); - - const productsBlockProductsList = await page.$$( - selectors.frontend.queryProductsList - ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - expect( productsBlockProductsList ).toHaveLength( 1 ); - - await expect( page ).toMatch( block.foundProduct ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - - it( 'should refresh the page only if the user click on button', async () => { - await goToTemplateEditor( { - postId: productCatalogTemplateId, - } ); - - await waitForCanvas(); - await selectBlockByName( block.slug ); - await enableApplyFiltersButton(); - await saveTemplate(); - await goToShopPage(); - - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.filter ); - await page.click( selectors.frontend.filter ); - await Promise.all( [ - page.waitForNavigation( { - waitUntil: 'networkidle0', - } ), - page.click( selectors.frontend.submitButton ), - ] ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - await page.waitForSelector( - selectors.frontend.classicProductsList - ); - const products = await page.$$( - selectors.frontend.classicProductsList - ); - - const productsBlockProductsList = await page.$$( - selectors.frontend.queryProductsList - ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - expect( productsBlockProductsList ).toHaveLength( 1 ); - await expect( page ).toMatch( block.foundProduct ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - } ); - - describe( 'with Product Query Block', () => { - let editorPageUrl = ''; - let frontedPageUrl = ''; - - useTheme( 'emptytheme' ); - beforeAll( async () => { - await switchUserToAdmin(); - await createNewPost( { - postType: 'post', - title: block.name, - } ); - - await insertBlock( 'Products (Beta)' ); - await insertBlock( block.name ); - await publishPost(); - - editorPageUrl = page.url(); - frontedPageUrl = await page.evaluate( () => - wp.data.select( 'core/editor' ).getPermalink() - ); - await page.goto( frontedPageUrl ); - } ); - - it( 'should show only products that match the filter', async () => { - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.filter ); - - await Promise.all( [ - page.waitForNavigation(), - page.click( selectors.frontend.filter ), - ] ); - - const products = await page.$$( - selectors.frontend.queryProductsList - ); - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - - it( 'should refresh the page only if the user clicks on button', async () => { - await page.goto( editorPageUrl ); - await waitForCanvas(); - await ensureSidebarOpened(); - await selectBlockByName( block.slug ); - await switchBlockInspectorTab( 'Settings' ); - await setCheckbox( - await getToggleIdByLabel( "Show 'Apply filters' button" ) - ); - - await saveOrPublish(); - await page.goto( frontedPageUrl ); - - const isRefreshed = jest.fn( () => void 0 ); - page.on( 'load', isRefreshed ); - - await page.waitForSelector( block.class + '.is-loading', { - hidden: true, - } ); - - expect( isRefreshed ).not.toBeCalled(); - - await page.waitForSelector( selectors.frontend.filter ); - await page.click( selectors.frontend.filter ); - await Promise.all( [ - page.waitForNavigation( { - waitUntil: 'networkidle0', - } ), - page.click( selectors.frontend.submitButton ), - ] ); - - const pageURL = page.url(); - const parsedURL = new URL( pageURL ); - - await page.waitForSelector( selectors.frontend.queryProductsList ); - const products = await page.$$( - selectors.frontend.queryProductsList - ); - - expect( isRefreshed ).toBeCalledTimes( 1 ); - expect( products ).toHaveLength( 1 ); - expect( parsedURL.search ).toEqual( - block.urlSearchParamWhenFilterIsApplied - ); - } ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/product-search.test.ts b/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/product-search.test.ts deleted file mode 100644 index 8d83902e2263b..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e-jest/specs/shopper/product-search.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Internal dependencies - */ -import { shopper } from '../../../utils'; -import { getTextContent } from '../../page-utils'; - -// These tests are skipped and previously relied on GUTENBERG_EDITOR_CONTEXT. -describe.skip( `Shopper → Product Search`, () => { - beforeEach( async () => { - await shopper.block.goToBlockPage( 'Product Search' ); - await page.waitForSelector( '.wp-block-search' ); - } ); - - it( 'should render product variation', async () => { - const [ postType ] = await getTextContent( - '.wp-block-search input[name="post_type"]' - ); - await expect( postType ).toBe( 'product' ); - } ); - - it( 'should be able to search for products', async () => { - await page.type( '.wp-block-search input[name="s"]', 'Stick' ); - - await Promise.all( [ - page.waitForNavigation(), - page.keyboard.press( 'Enter' ), - ] ); - - const products = await page.$$( 'ul.products.columns-3 > li' ); - - expect( products ).toHaveLength( 2 ); - - const productTitles = await getTextContent( - 'ul.products.columns-3 .woocommerce-loop-product__title' - ); - - expect( productTitles ).toContain( '32GB USB Stick' ); - expect( productTitles ).toContain( '128GB USB Stick' ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/.eslintrc.js b/plugins/woocommerce-blocks/tests/e2e/.eslintrc.js index cbe964d4e572b..118f61e87070c 100644 --- a/plugins/woocommerce-blocks/tests/e2e/.eslintrc.js +++ b/plugins/woocommerce-blocks/tests/e2e/.eslintrc.js @@ -23,6 +23,7 @@ const config = { 'playwright/prefer-web-first-assertions': 'error', 'playwright/valid-expect': 'error', 'rulesdir/no-raw-playwright-test-import': 'error', + 'playwright/no-hooks': [ 'error', { allow: [ 'beforeEach' ] } ], }, }; diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/scripts/parallel/languages.sh b/plugins/woocommerce-blocks/tests/e2e/bin/scripts/parallel/languages.sh new file mode 100644 index 0000000000000..901fbc4d01528 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/bin/scripts/parallel/languages.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +wp language core install nl_NL +wp language plugin install woocommerce nl_NL diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/scripts/parallel/themes.sh b/plugins/woocommerce-blocks/tests/e2e/bin/scripts/parallel/themes.sh deleted file mode 100644 index e840317605bf7..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/bin/scripts/parallel/themes.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -# Get the directory of the current script. -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# The theme dir is two levels up from where this script is running. -themes_dir="$script_dir/../../themes" - -# Delete the child themes if they already exist. -wp theme delete storefront-child__block-notices-filter -wp theme delete storefront-child__block-notices-template -wp theme delete storefront-child__classic-notices-template -wp theme delete twentytwentyfour-child__block-notices-filter -wp theme delete twentytwentyfour-child__block-notices-template -wp theme delete twentytwentyfour-child__classic-notices-template - -# Install the child themes. -wp theme install "$themes_dir/storefront-child__block-notices-filter.zip" -wp theme install "$themes_dir/storefront-child__block-notices-template.zip" -wp theme install "$themes_dir/storefront-child__classic-notices-template.zip" -wp theme install "$themes_dir/twentytwentyfour-child__block-notices-filter.zip" -wp theme install "$themes_dir/twentytwentyfour-child__block-notices-template.zip" -wp theme install "$themes_dir/twentytwentyfour-child__classic-notices-template.zip" diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/test-env-setup.sh b/plugins/woocommerce-blocks/tests/e2e/bin/test-env-setup.sh index 170c7dab2c626..0ca83f8e72416 100755 --- a/plugins/woocommerce-blocks/tests/e2e/bin/test-env-setup.sh +++ b/plugins/woocommerce-blocks/tests/e2e/bin/test-env-setup.sh @@ -6,33 +6,6 @@ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" head_dir=$(cd "$(dirname "$script_dir")" && cd ../../.. && pwd) relative_path=${script_dir#$head_dir/} -# Generate the child themes zip files before running the tests. -# By doing so, we ensure that the zip files are up-to-date. -themes_dir="$script_dir/themes" -themes=( - "storefront-child__block-notices-filter" - "storefront-child__block-notices-template" - "storefront-child__classic-notices-template" - "twentytwentyfour-child__block-notices-filter" - "twentytwentyfour-child__block-notices-template" - "twentytwentyfour-child__classic-notices-template" -) -for theme in "${themes[@]}"; do - # Define the path to the theme directory and the zip file. - theme_dir="$themes_dir/$theme" - zip_file="$themes_dir/$theme.zip" - - # Check if the zip file exists. If it does, delete it. - if [ -f "$zip_file" ]; then - echo "Deleting existing zip file for $theme." - rm "$zip_file" - fi - - # Navigate to the themes directory to ensure the zip contains only the theme folder name. - # Then, create a fresh zip file. - echo "Creating zip file for $theme." - (cd "$themes_dir" && zip -r "$zip_file" "$theme" -x "*.git*" -x "*node_modules*") -done - # Run the main script in the container for better performance. wp-env run tests-cli -- bash wp-content/plugins/woocommerce/blocks-bin/playwright/scripts/index.sh +wp-env run tests-cli wp option update woocommerce_coming_soon 'no' diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-filter/style.css b/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-filter/style.css deleted file mode 100644 index 57c8d0aeddf07..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-filter/style.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Theme Name: Storefront Child (Block Notices by filter) -Template: storefront -*/ diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-template/style.css b/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-template/style.css deleted file mode 100644 index 85cab56e97dbe..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-template/style.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Theme Name: Storefront Child (Block Notices by template) -Template: storefront -*/ diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__classic-notices-template/style.css b/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__classic-notices-template/style.css deleted file mode 100644 index ea930d81e41c7..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__classic-notices-template/style.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Theme Name: Storefront Child (Classic Notices by template) -Template: storefront -*/ diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-filter/style.css b/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-filter/style.css deleted file mode 100644 index f6079e9bf4e58..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-filter/style.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Theme Name: Twenty Twenty-Four Child (Block Notices by filter) -Template: twentytwentyfour -*/ diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-template/style.css b/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-template/style.css deleted file mode 100644 index 135652f318848..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-template/style.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Theme Name: Twenty Twenty-Four Child (Block Notices by template) -Template: twentytwentyfour -*/ diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__classic-notices-template/style.css b/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__classic-notices-template/style.css deleted file mode 100644 index dd914af1d24d3..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__classic-notices-template/style.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Theme Name: Twenty Twenty-Four Child (Classic Notices by template) -Template: twentytwentyfour -*/ diff --git a/plugins/woocommerce-blocks/tests/e2e/block-theme-with-templates.setup.ts b/plugins/woocommerce-blocks/tests/e2e/block-theme-with-templates.setup.ts index 4340f77260ce4..8c26b4a04ee1b 100644 --- a/plugins/woocommerce-blocks/tests/e2e/block-theme-with-templates.setup.ts +++ b/plugins/woocommerce-blocks/tests/e2e/block-theme-with-templates.setup.ts @@ -1,9 +1,19 @@ /** * External dependencies */ -import { BLOCK_THEME_WITH_TEMPLATES_SLUG } from '@woocommerce/e2e-utils'; -import { test as setup } from '@woocommerce/e2e-playwright-utils'; +import { + BLOCK_THEME_WITH_TEMPLATES_SLUG, + DB_EXPORT_FILE, + cli, +} from '@woocommerce/e2e-utils'; +import { test as setup, expect } from '@woocommerce/e2e-playwright-utils'; setup( 'Sets up the block theme with templates', async ( { requestUtils } ) => { await requestUtils.activateTheme( BLOCK_THEME_WITH_TEMPLATES_SLUG ); + + const cliOutput = await cli( + `npm run wp-env run tests-cli wp db export ${ DB_EXPORT_FILE }` + ); + // eslint-disable-next-line playwright/no-standalone-expect + expect( cliOutput.stdout ).toContain( 'Success: Exported' ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/block-theme.setup.ts b/plugins/woocommerce-blocks/tests/e2e/block-theme.setup.ts index 7d453543af83a..940d5e76a2d3e 100644 --- a/plugins/woocommerce-blocks/tests/e2e/block-theme.setup.ts +++ b/plugins/woocommerce-blocks/tests/e2e/block-theme.setup.ts @@ -1,7 +1,8 @@ +/* eslint-disable playwright/no-standalone-expect */ /** * External dependencies */ -import { BLOCK_THEME_SLUG, cli } from '@woocommerce/e2e-utils'; +import { BLOCK_THEME_SLUG, DB_EXPORT_FILE, cli } from '@woocommerce/e2e-utils'; import { test as setup, expect } from '@woocommerce/e2e-playwright-utils'; setup( 'Sets up the block theme', async () => { @@ -21,4 +22,9 @@ setup( 'Sets up the block theme', async () => { expect( cliOutput.stdout ).toContain( 'Success: Rewrite structure set' ); expect( cliOutput.stdout ).toContain( 'Success: Rewrite rules flushed' ); + + cliOutput = await cli( + `npm run wp-env run tests-cli wp db export ${ DB_EXPORT_FILE }` + ); + expect( cliOutput.stdout ).toContain( 'Success: Exported' ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/classic-theme.setup.ts b/plugins/woocommerce-blocks/tests/e2e/classic-theme.setup.ts index dda016ac3cf17..93a74319f79fc 100644 --- a/plugins/woocommerce-blocks/tests/e2e/classic-theme.setup.ts +++ b/plugins/woocommerce-blocks/tests/e2e/classic-theme.setup.ts @@ -1,9 +1,11 @@ +/* eslint-disable playwright/no-standalone-expect */ /** * External dependencies */ import { CLASSIC_THEME_NAME, CLASSIC_THEME_SLUG, + DB_EXPORT_FILE, cli, } from '@woocommerce/e2e-utils'; import { test as setup, expect } from '@woocommerce/e2e-playwright-utils'; @@ -16,4 +18,9 @@ setup( 'Sets up the classic theme', async ( { admin } ) => { await expect( admin.page.getByText( `Active: ${ CLASSIC_THEME_NAME }` ) ).toBeVisible(); + + const cliOutput = await cli( + `npm run wp-env run tests-cli wp db export ${ DB_EXPORT_FILE }` + ); + expect( cliOutput.stdout ).toContain( 'Success: Exported' ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/global-setup.ts b/plugins/woocommerce-blocks/tests/e2e/global-setup.ts index 5e19112b90537..3a46c6a9fa55c 100644 --- a/plugins/woocommerce-blocks/tests/e2e/global-setup.ts +++ b/plugins/woocommerce-blocks/tests/e2e/global-setup.ts @@ -41,45 +41,6 @@ const prepareAttributes = async () => { .getByRole( 'button' ) .click(); - await page.goto( BASE_URL + '/wp-admin/post-new.php' ); - - await page.waitForFunction( () => { - return window.wp.data !== undefined; - } ); - - // Disable the welcome guide for the site editor. - await page.evaluate( () => { - return Promise.all( [ - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-site', 'welcomeGuide', false ), - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-site', 'welcomeGuideStyles', false ), - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-site', 'welcomeGuidePage', false ), - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-site', 'welcomeGuideTemplate', false ), - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-post', 'welcomeGuide', false ), - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-post', 'welcomeGuideStyles', false ), - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-post', 'welcomeGuidePage', false ), - - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-post', 'welcomeGuideTemplate', false ), - ] ); - } ); - - await page.context().storageState( { path: adminFile } ); - await context.close(); await browser.close(); diff --git a/plugins/woocommerce-blocks/tests/e2e/mocks/custom-plugins/index.ts b/plugins/woocommerce-blocks/tests/e2e/mocks/custom-plugins/index.ts deleted file mode 100644 index 04bca77e0dec4..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/mocks/custom-plugins/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './utils'; diff --git a/plugins/woocommerce-blocks/tests/e2e/mocks/custom-plugins/readme.md b/plugins/woocommerce-blocks/tests/e2e/mocks/custom-plugins/readme.md deleted file mode 100644 index f37b4045c0938..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/mocks/custom-plugins/readme.md +++ /dev/null @@ -1,70 +0,0 @@ -# Testing WordPress actions and filters - -This documentation covers testing WordPress actions and filters when writing Playwright tests. - -## Table of Contents - -- [Description](#description) -- [Usage](#usage) - -## Description - -You can test an action or filter (change) by creating a custom WordPress plugin, installing it and when you are done, removing it. - -The 3 functions responsible are located inside of `tests/e2e-pw/mocks/custom-plugins/utils.ts`: - -- `createPluginFromPHPFile()` -- `installPluginFromPHPFile()` -- `uninstallPluginFromPHPFile()` - -## Usage - -### Example: Testing a custom Add to Cart text - -1. Create the custom plugin file. - -`update-product-button-text.php`. - -```php - { - await installPluginFromPHPFile( - `${ __dirname }/update-product-button-text.php` - ); - await frontendUtils.goToShop(); - const blocks = await frontendUtils.getBlockByName( blockData.name ); - const buttonWithNewText = await blocks.getByText( 'Buy Now' ).count(); - - const productsDisplayed = 16; - expect( buttonWithNewText ).toEqual( productsDisplayed ); -} ); -``` - -3. Remove the plugin when done testing. - -```javascript -test.afterAll( async () => { - await uninstallPluginFromPHPFile( - `${ __dirname }/update-product-button-text.php` - ); -} ); -``` - -In the above example, the test checks whether the filter `woocommerce_product_add_to_cart_text` is applied correctly. It installs the "Custom Add to Cart Text" plugin, navigates to the shop page using `frontendUtils`, and verifies if the "Buy Now" button text appears as expected. Finally, it cleans the cart and uninstalls the plugin. - -You can adapt this example to test other filters and actions by modifying the code accordingly. diff --git a/plugins/woocommerce-blocks/tests/e2e/mocks/custom-plugins/utils.ts b/plugins/woocommerce-blocks/tests/e2e/mocks/custom-plugins/utils.ts deleted file mode 100644 index 673f278fe761d..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/mocks/custom-plugins/utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * External dependencies - */ -import { cli } from '@woocommerce/e2e-utils'; -import path from 'path'; - -const createPluginFromPHPFile = async ( phpFilePath: string ) => { - const absolutePath = path.resolve( phpFilePath ); - const directory = path.dirname( absolutePath ); - const fileName = path.basename( phpFilePath ); - const fileNameZip = fileName.replace( '.php', '' ); - await cli( - `cd ${ directory } && zip ${ fileNameZip }.zip ${ fileName } && mv ${ fileNameZip }.zip ${ __dirname }` - ); -}; - -export const installPluginFromPHPFile = async ( phpFilePath: string ) => { - await createPluginFromPHPFile( phpFilePath ); - const fileName = path.basename( phpFilePath ).replace( '.php', '' ); - await cli( - `npm run wp-env run tests-cli -- wp plugin install /var/www/html/custom-plugins/${ fileName }.zip --activate` - ); -}; - -export const uninstallPluginFromPHPFile = async ( phpFilePath: string ) => { - const fileName = path.basename( phpFilePath ).replace( '.php', '' ); - await cli( - `npm run wp-env run tests-cli -- wp plugin delete ${ fileName }` - ); - await cli( `rm ${ __dirname }/${ fileName }.zip` ); -}; diff --git a/plugins/woocommerce-blocks/tests/e2e/playwright-utils/test.ts b/plugins/woocommerce-blocks/tests/e2e/playwright-utils/test.ts index 1fad1416dd765..ece2f02e47393 100644 --- a/plugins/woocommerce-blocks/tests/e2e/playwright-utils/test.ts +++ b/plugins/woocommerce-blocks/tests/e2e/playwright-utils/test.ts @@ -13,6 +13,7 @@ import { import { TemplateApiUtils, STORAGE_STATE_PATH, + DB_EXPORT_FILE, EditorUtils, FrontendUtils, StoreApiUtils, @@ -21,6 +22,7 @@ import { LocalPickupUtils, MiniCartUtils, WPCLIUtils, + cli, } from '@woocommerce/e2e-utils'; import { Post } from '@wordpress/e2e-test-utils-playwright/build-types/request-utils/posts'; @@ -158,14 +160,21 @@ const test = base.extend< await page.evaluate( () => { window.localStorage.clear(); } ); + + const cliOutput = await cli( + `npm run wp-env run tests-cli wp db import ${ DB_EXPORT_FILE }` + ); + if ( ! cliOutput.stdout.includes( 'Success: Imported ' ) ) { + throw new Error( `Failed to import ${ DB_EXPORT_FILE }` ); + } }, pageUtils: async ( { page }, use ) => { await use( new PageUtils( { page } ) ); }, templateApiUtils: async ( {}, use ) => await use( new TemplateApiUtils( baseRequest ) ), - editorUtils: async ( { editor, page }, use ) => { - await use( new EditorUtils( editor, page ) ); + editorUtils: async ( { editor, page, admin }, use ) => { + await use( new EditorUtils( editor, page, admin ) ); }, frontendUtils: async ( { page, requestUtils }, use ) => { await use( new FrontendUtils( page, requestUtils ) ); diff --git a/plugins/woocommerce-blocks/tests/e2e/playwright.performance.config.ts b/plugins/woocommerce-blocks/tests/e2e/playwright.performance.config.ts new file mode 100644 index 0000000000000..b7db4d75e0d43 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/playwright.performance.config.ts @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { defineConfig, PlaywrightTestConfig } from '@playwright/test'; + +/** + * Internal dependencies + */ +import baseConfig from './playwright.config'; + +const config: PlaywrightTestConfig = { + ...baseConfig, + projects: [ + { + name: 'chromium', + testDir: '.', + testMatch: '*.perf.ts', + }, + ], +}; + +export default defineConfig( config ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-checkout-fields-plugin.php b/plugins/woocommerce-blocks/tests/e2e/plugins/additional-checkout-fields.php similarity index 86% rename from plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-checkout-fields-plugin.php rename to plugins/woocommerce-blocks/tests/e2e/plugins/additional-checkout-fields.php index 39d9b9676e86e..a6a7954f761ec 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-checkout-fields-plugin.php +++ b/plugins/woocommerce-blocks/tests/e2e/plugins/additional-checkout-fields.php @@ -1,8 +1,11 @@ 'first-plugin-namespace/government-ID', 'label' => 'Government ID', @@ -67,7 +70,7 @@ public function register_custom_checkout_fields() { }, ), ); - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'first-plugin-namespace/confirm-government-ID', 'label' => 'Confirm government ID', @@ -85,7 +88,7 @@ public function register_custom_checkout_fields() { }, ), ); - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'first-plugin-namespace/truck-size-ok', 'label' => 'Can a truck fit down your road?', @@ -93,7 +96,7 @@ public function register_custom_checkout_fields() { 'type' => 'checkbox', ) ); - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'first-plugin-namespace/road-size', 'label' => 'How wide is your road?', @@ -118,7 +121,7 @@ public function register_custom_checkout_fields() { // Fake sanitization function that removes full stops from the Government ID string. add_filter( - '__experimental_woocommerce_blocks_sanitize_additional_field', + 'woocommerce_sanitize_additional_field', function ( $field_value, $field_key ) { if ( 'first-plugin-namespace/government-ID' === $field_key ) { $field_value = str_replace( '.', '', $field_value ); @@ -130,7 +133,7 @@ function ( $field_value, $field_key ) { ); add_action( - '__experimental_woocommerce_blocks_validate_additional_field', + 'woocommerce_validate_additional_field', function ( WP_Error $errors, $field_key, $field_value ) { if ( 'first-plugin-namespace/government-ID' === $field_key || 'first-plugin-namespace/confirm-government-ID' === $field_key ) { $match = preg_match( '/[A-Z0-9]{5}/', $field_value ); @@ -144,7 +147,7 @@ function ( WP_Error $errors, $field_key, $field_value ) { ); add_action( - '__experimental_woocommerce_blocks_validate_location_address_fields', + 'woocommerce_blocks_validate_location_address_fields', function ( \WP_Error $errors, $fields, $group ) { if ( $fields['first-plugin-namespace/government-ID'] !== $fields['first-plugin-namespace/confirm-government-ID'] ) { $errors->add( 'gov_id_mismatch', 'Please ensure your government ID matches the confirmation.' ); @@ -155,7 +158,7 @@ function ( \WP_Error $errors, $fields, $group ) { ); // Contact fields, one checkbox, select, and text input. - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'second-plugin-namespace/marketing-opt-in', 'label' => 'Do you want to subscribe to our newsletter?', @@ -163,7 +166,7 @@ function ( \WP_Error $errors, $fields, $group ) { 'type' => 'checkbox', ) ); - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'second-plugin-namespace/gift-message-in-package', 'label' => 'Enter a gift message to include in the package', @@ -171,7 +174,7 @@ function ( \WP_Error $errors, $fields, $group ) { 'type' => 'text', ) ); - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'second-plugin-namespace/type-of-purchase', 'label' => 'Is this a personal purchase or a business purchase?', @@ -193,29 +196,29 @@ function ( \WP_Error $errors, $fields, $group ) { // A field of each type in additional information section. - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'third-plugin-namespace/please-send-me-a-free-gift', 'label' => 'Would you like a free gift with your order?', - 'location' => 'additional', + 'location' => 'order', 'type' => 'checkbox', ) ); - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'third-plugin-namespace/what-is-your-favourite-colour', 'label' => 'What is your favourite colour?', - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', ) ); - __experimental_woocommerce_blocks_register_checkout_field( + woocommerce_register_additional_checkout_field( array( 'id' => 'third-plugin-namespace/how-did-you-hear-about-us', 'label' => 'How did you hear about us?', - 'location' => 'additional', + 'location' => 'order', 'type' => 'select', 'options' => array( array( diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-button/update-product-button-text.php b/plugins/woocommerce-blocks/tests/e2e/plugins/custom-add-to-cart-button-text.php similarity index 56% rename from plugins/woocommerce-blocks/tests/e2e/tests/product-button/update-product-button-text.php rename to plugins/woocommerce-blocks/tests/e2e/plugins/custom-add-to-cart-button-text.php index 37e44f2f208ca..16e3ddafacf7b 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-button/update-product-button-text.php +++ b/plugins/woocommerce-blocks/tests/e2e/plugins/custom-add-to-cart-button-text.php @@ -1,16 +1,13 @@ get_cart() as $hash => $value ) { + $value['data']->set_price( 50 ); + } +} + +add_action( 'woocommerce_before_calculate_totals', 'calc_price' ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/all-products/all-products.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/all-products/all-products.block_theme.spec.ts new file mode 100644 index 0000000000000..7746283097c3a --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/all-products/all-products.block_theme.spec.ts @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +/** + * Internal dependencies + */ + +const BLOCK_NAME = 'woocommerce/all-products'; + +test.describe( `${ BLOCK_NAME } Block`, () => { + test.beforeEach( async ( { admin, editor } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: BLOCK_NAME } ); + } ); + + test( 'block can be inserted and it is rendered on the frontend', async ( { + editorUtils, + page, + } ) => { + await editorUtils.publishAndVisitPost(); + + await expect( + page.locator( '.wc-block-grid__product.wc-block-layout' ) + ).toHaveCount( 9 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/attributes-filter/attribute-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/attributes-filter/attribute-filter.block_theme.spec.ts new file mode 100644 index 0000000000000..04c7986da6140 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/attributes-filter/attribute-filter.block_theme.spec.ts @@ -0,0 +1,296 @@ +/** + * External dependencies + */ +import { test as base, expect } from '@woocommerce/e2e-playwright-utils'; +import { cli } from '@woocommerce/e2e-utils'; + +/** + * Internal dependencies + */ +import ProductCollectionPage from '../product-collection/product-collection.page'; + +const blockData = { + name: 'Filter by Attribute', + slug: 'woocommerce/attribute-filter', + urlSearchParamWhenFilterIsApplied: 'filter_size=small&query_type_size=or', +}; + +const test = base.extend< { + productCollectionPageObject: ProductCollectionPage; +} >( { + productCollectionPageObject: async ( + { page, admin, editor, templateApiUtils, editorUtils }, + use + ) => { + const pageObject = new ProductCollectionPage( { + page, + admin, + editor, + templateApiUtils, + editorUtils, + } ); + await use( pageObject ); + }, +} ); + +test.describe( `${ blockData.name } Block`, () => { + test.beforeEach( async ( { admin, editor, editorUtils } ) => { + await admin.createNewPost(); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'attribute-filter', + heading: 'Filter By Attribute', + }, + } ); + const attributeFilter = await editorUtils.getBlockByName( + blockData.slug + ); + + await attributeFilter.getByText( 'Size' ).click(); + await attributeFilter.getByText( 'Done' ).click(); + await editor.openDocumentSettingsSidebar(); + } ); + + test( "should allow changing the block's title", async ( { page } ) => { + const textSelector = + '.wp-block-woocommerce-filter-wrapper .wp-block-heading'; + + const title = 'New Title'; + + await page.locator( textSelector ).fill( title ); + + await expect( page.locator( textSelector ) ).toHaveText( title ); + } ); + + test( 'should allow changing the display style', async ( { + page, + editorUtils, + editor, + } ) => { + const attributeFilter = await editorUtils.getBlockByName( + blockData.slug + ); + await editor.selectBlocks( attributeFilter ); + + await expect( + page.getByRole( 'checkbox', { name: 'Small' } ) + ).toBeVisible(); + + await page.getByLabel( 'DropDown' ).click(); + + await expect( + attributeFilter.getByRole( 'checkbox', { + name: 'Small', + } ) + ).toBeHidden(); + + await expect( + page.getByRole( 'checkbox', { name: 'Small' } ) + ).toBeHidden(); + + await expect( page.getByRole( 'combobox' ) ).toBeVisible(); + } ); + + test( 'should allow toggling the visibility of the filter button', async ( { + page, + editorUtils, + editor, + } ) => { + const attributeFilter = await editorUtils.getBlockByName( + blockData.slug + ); + await editor.selectBlocks( attributeFilter ); + + await expect( + attributeFilter.getByRole( 'button', { + name: 'Apply', + } ) + ).toBeHidden(); + + await page.getByText( "Show 'Apply filters' button" ).click(); + + await expect( + attributeFilter.getByRole( 'button', { + name: 'Apply', + } ) + ).toBeVisible(); + } ); +} ); + +test.describe( `${ blockData.name } Block - with PHP classic template`, () => { + test.beforeEach( async ( { admin, page, editor, editorUtils } ) => { + await cli( + 'npm run wp-env run tests-cli -- wp option update wc_blocks_use_blockified_product_grid_block_as_template false' + ); + + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//archive-product', + postType: 'wp_template', + } ); + + await editorUtils.enterEditMode(); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'attribute-filter', + heading: 'Filter By Attribute', + }, + } ); + const attributeFilter = await editorUtils.getBlockByName( + blockData.slug + ); + + await attributeFilter.getByText( 'Size' ).click(); + await attributeFilter.getByText( 'Done' ).click(); + + await editor.saveSiteEditorEntities(); + await page.goto( `/shop` ); + } ); + + test( 'should show all products', async ( { frontendUtils, page } ) => { + const legacyTemplate = await frontendUtils.getBlockByName( + 'woocommerce/legacy-template' + ); + + const products = legacyTemplate + .getByRole( 'list' ) + .locator( '.product' ); + + await expect( products ).toHaveCount( 16 ); + + await expect( + page.getByRole( 'checkbox', { name: 'Small' } ) + ).toBeVisible(); + + await expect( + page.getByRole( 'checkbox', { name: 'Medium' } ) + ).toBeVisible(); + + await expect( + page.getByRole( 'checkbox', { name: 'Large' } ) + ).toBeVisible(); + } ); + + test( 'should show only products that match the filter', async ( { + frontendUtils, + page, + } ) => { + await page.getByRole( 'checkbox', { name: 'Small' } ).click(); + + const legacyTemplate = await frontendUtils.getBlockByName( + 'woocommerce/legacy-template' + ); + + const products = legacyTemplate + .getByRole( 'list' ) + .locator( '.product' ); + + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + await expect( products ).toHaveCount( 1 ); + } ); +} ); + +test.describe( `${ blockData.name } Block - with Product Collection`, () => { + test.beforeEach( + async ( { + admin, + editorUtils, + productCollectionPageObject, + editor, + } ) => { + await admin.createNewPost(); + await productCollectionPageObject.insertProductCollection(); + await productCollectionPageObject.chooseCollectionInPost( + 'productCatalog' + ); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'attribute-filter', + heading: 'Filter By Attribute', + }, + } ); + + const attributeFilter = await editorUtils.getBlockByName( + blockData.slug + ); + + await attributeFilter.getByText( 'Size' ).click(); + await attributeFilter.getByText( 'Done' ).click(); + + await editorUtils.publishAndVisitPost(); + } + ); + + test( 'should show all products', async ( { page } ) => { + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 9 ); + } ); + + test( 'should show only products that match the filter', async ( { + page, + } ) => { + await page.getByRole( 'checkbox', { name: 'Small' } ).click(); + + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 1 ); + } ); + + test( 'should refresh the page only if the user clicks on button', async ( { + page, + admin, + editor, + editorUtils, + productCollectionPageObject, + } ) => { + await admin.createNewPost(); + await productCollectionPageObject.insertProductCollection(); + await productCollectionPageObject.chooseCollectionInPost( + 'productCatalog' + ); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'attribute-filter', + heading: 'Filter By Attribute', + }, + } ); + const attributeFilterControl = await editorUtils.getBlockByName( + blockData.slug + ); + await attributeFilterControl.getByText( 'Size' ).click(); + await attributeFilterControl.getByText( 'Done' ).click(); + + await editor.selectBlocks( attributeFilterControl ); + await editor.openDocumentSettingsSidebar(); + await page.getByText( "Show 'Apply filters' button" ).click(); + await editorUtils.publishAndVisitPost(); + + await page.getByRole( 'checkbox', { name: 'Small' } ).click(); + await page.getByRole( 'button', { name: 'Apply' } ).click(); + + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 1 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/basic.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/basic.block_theme.spec.ts index ab2264178b8ef..9c762201fdf67 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/basic.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/basic.block_theme.spec.ts @@ -2,36 +2,49 @@ * External dependencies */ import { test, expect } from '@woocommerce/e2e-playwright-utils'; +import { customerFile, guestFile } from '@woocommerce/e2e-utils'; -/** - * Internal dependencies - */ +test.describe( 'Basic role-based functionality tests', () => { + test.describe( 'As admin', () => { + // Admin is the default user, so no need to set storage state. + test( 'Load Dashboard page', async ( { page } ) => { + await page.goto( '/wp-admin' ); -test.describe( 'A basic set of tests to ensure WP, wp-admin and my-account load', async () => { - test( 'Load the home page', async ( { page } ) => { - await page.goto( '/' ); - const title = page - .locator( 'header' ) - .locator( '.wp-block-site-title' ); - await expect( title ).toHaveText( 'WooCommerce Blocks E2E Test Suite' ); + await expect( + page.getByRole( 'heading', { name: 'Dashboard' } ) + ).toHaveText( 'Dashboard' ); + } ); } ); - test.describe( 'Sign in as admin', () => { - test( 'Load wp-admin', async ( { page } ) => { - await page.goto( '/wp-admin' ); - const title = page.locator( 'div.wrap > h1' ); - await expect( title ).toHaveText( 'Dashboard' ); + test.describe( 'As customer', () => { + test.use( { + storageState: customerFile, + } ); + test( 'Load My Account page', async ( { page } ) => { + await page.goto( '/my-account' ); + + await expect( + page.getByRole( 'heading', { name: 'My Account' } ) + ).toBeVisible(); + await expect( + page.getByText( 'Hello Jane Smith (not Jane Smith? Log out)' ) + ).toBeVisible(); } ); } ); - test.describe( 'Sign in as customer', () => { + test.describe( 'As guest', () => { test.use( { - storageState: process.env.CUSTOMERSTATE, + storageState: guestFile, } ); - test( 'Load customer my account page', async ( { page } ) => { - await page.goto( '/my-account' ); - const title = page.locator( 'h1.wp-block-post-title' ); - await expect( title ).toHaveText( 'My Account' ); + + test( 'Load home page', async ( { page } ) => { + await page.goto( '/' ); + + await expect( + page.getByRole( 'banner' ).getByRole( 'link', { + name: 'WooCommerce Blocks E2E Test', + } ) + ).toBeVisible(); } ); } ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/breadcrumbs/breadcrumbs.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/breadcrumbs/breadcrumbs.block_theme.spec.ts new file mode 100644 index 0000000000000..e24f8905a5c90 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/breadcrumbs/breadcrumbs.block_theme.spec.ts @@ -0,0 +1,73 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +/** + * Internal dependencies + */ + +const blockData = { + slug: 'woocommerce/breadcrumbs', + name: 'Store Breadcrumbs', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( "block can't be inserted in Post Editor", async ( { + admin, + editor, + } ) => { + await admin.createNewPost(); + await expect( + editor.insertBlock( { name: blockData.slug } ) + ).rejects.toThrow( + new RegExp( `Block type '${ blockData.slug }' is not registered.` ) + ); + } ); + + test( 'block should be already added in the Product Catalog Template', async ( { + editorUtils, + admin, + } ) => { + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//archive-product', + postType: 'wp_template', + } ); + await editorUtils.enterEditMode(); + const alreadyPresentBlock = await editorUtils.getBlockByName( + blockData.slug + ); + + await expect( alreadyPresentBlock ).toHaveText( + 'Breadcrumbs / Navigation / Path' + ); + } ); + + test( 'block can be inserted in the Site Editor', async ( { + admin, + requestUtils, + editorUtils, + editor, + } ) => { + const template = await requestUtils.createTemplate( 'wp_template', { + slug: 'sorter', + title: 'Sorter', + content: 'howdy', + } ); + + await admin.visitSiteEditor( { + postId: template.id, + postType: 'wp_template', + } ); + + await editorUtils.enterEditMode(); + + await editor.insertBlock( { + name: blockData.slug, + } ); + + const block = await editorUtils.getBlockByName( blockData.slug ); + + await expect( block ).toHaveText( 'Breadcrumbs / Navigation / Path' ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.performace.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.perf.ts similarity index 97% rename from plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.performace.block_theme.side_effects.spec.ts rename to plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.perf.ts index 480609dbd4ac3..1d08ddaaa373d 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.performace.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.perf.ts @@ -21,7 +21,6 @@ const test = base.extend< { pageObject: CartPage } >( { test.describe( 'Cart performance', () => { test.beforeEach( async ( { frontendUtils } ) => { await frontendUtils.goToShop(); - await frontendUtils.emptyCart(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); } ); @@ -132,7 +131,7 @@ test.describe( 'Cart performance', () => { 'button.wc-block-components-quantity-selector__button--plus' ) .waitFor(); - await page.click( '.wc-block-components-totals-coupon-link' ); + await page.click( '.wc-block-components-totals-coupon button' ); const timesForResponse = []; let i = 3; diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.shopper.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.shopper.block_theme.side_effects.spec.ts index ebed24288d455..5b6e9edd2735b 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.shopper.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-block.shopper.block_theme.side_effects.spec.ts @@ -2,10 +2,6 @@ * External dependencies */ import { test as base, expect } from '@woocommerce/e2e-playwright-utils'; -import { - installPluginFromPHPFile, - uninstallPluginFromPHPFile, -} from '@woocommerce/e2e-mocks/custom-plugins'; /** * Internal dependencies @@ -33,7 +29,6 @@ test.describe( 'Shopper → Cart block', () => { frontendUtils, } ) => { await frontendUtils.goToShop(); - await frontendUtils.emptyCart(); await frontendUtils.addToCart( DISCOUNTED_PRODUCT_NAME ); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToCart(); @@ -68,13 +63,14 @@ test.describe( 'Shopper → Cart block', () => { test( 'Products with updated prices should not display a discount label', async ( { pageObject, + requestUtils, frontendUtils, } ) => { - await installPluginFromPHPFile( - `${ __dirname }/update-price-plugin.php` + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-update-price' ); + await frontendUtils.goToShop(); - await frontendUtils.emptyCart(); await frontendUtils.addToCart( DISCOUNTED_PRODUCT_NAME ); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToCart(); @@ -125,17 +121,12 @@ test.describe( 'Shopper → Cart block', () => { // Get the text content of the price element and check the price const hoodiePriceText = await hoodiePriceElement.textContent(); expect( hoodiePriceText ).toBe( '$50.00' ); - - await uninstallPluginFromPHPFile( - `${ __dirname }/update-price-plugin.php` - ); } ); test( 'User can view empty cart message', async ( { frontendUtils, page, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToCart(); // Verify cart is empty @@ -182,7 +173,7 @@ test.describe( 'Shopper → Cart block', () => { // Verify the "Proceed to Checkout" button is disabled during network request await expect( - page.getByRole( 'button', { name: 'Proceed to Checkout' } ) + page.getByRole( 'link', { name: 'Proceed to Checkout' } ) ).toBeDisabled(); // Verify the "Proceed to Checkout" button is enabled after network request @@ -204,7 +195,7 @@ test.describe( 'Shopper → Cart block', () => { .click(); // Verify the "Proceed to Checkout" button is disabled during network request await expect( - page.getByRole( 'button', { name: 'Proceed to Checkout' } ) + page.getByRole( 'link', { name: 'Proceed to Checkout' } ) ).toBeDisabled(); // Verify the "Proceed to Checkout" button is enabled after network request @@ -224,7 +215,7 @@ test.describe( 'Shopper → Cart block', () => { .click(); // Verify the "Proceed to Checkout" button is disabled during network request await expect( - page.getByRole( 'button', { name: 'Proceed to Checkout' } ) + page.getByRole( 'link', { name: 'Proceed to Checkout' } ) ).toBeDisabled(); // Verify the "Proceed to Checkout" button is enabled after network request @@ -257,7 +248,6 @@ test.describe( 'Shopper → Cart block', () => { frontendUtils, page, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCart(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-coupons.shopper.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-coupons.shopper.block_theme.spec.ts index 054b584c52dc5..7d8555fc7c461 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-coupons.shopper.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-coupons.shopper.block_theme.spec.ts @@ -26,26 +26,16 @@ test.describe( 'Shopper → Coupon', () => { ); } ); - test.afterEach( async ( { wpCliUtils } ) => { - const couponId = await wpCliUtils.getCouponIDByCode( - 'single-use-coupon' - ); - await cli( - `npm run wp-env run tests-cli -- wp wc shop_coupon delete ${ couponId } --force=1 --user=1` - ); - } ); - test( 'Logged in user can apply single-use coupon and place order', async ( { checkoutPageObject, frontendUtils, page, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToCart(); - await page.getByLabel( 'Add a coupon' ).click(); + await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByLabel( 'Enter code' ).fill( 'single-use-coupon' ); await page.getByRole( 'button', { name: 'Apply' } ).click(); @@ -60,7 +50,7 @@ test.describe( 'Shopper → Coupon', () => { ).toBeHidden(); await frontendUtils.goToCheckout(); - await page.getByLabel( 'Add a coupon' ).click(); + await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByLabel( 'Enter code' ).fill( 'single-use-coupon' ); await page.getByRole( 'button', { name: 'Apply' } ).click(); @@ -89,12 +79,11 @@ test.describe( 'Shopper → Coupon', () => { frontendUtils, page, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToCheckout(); - await page.getByLabel( 'Add a coupon' ).click(); + await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByLabel( 'Enter code' ).fill( 'single-use-coupon' ); await page.getByRole( 'button', { name: 'Apply' } ).click(); @@ -110,7 +99,7 @@ test.describe( 'Shopper → Coupon', () => { await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToCheckout(); - await page.getByLabel( 'Add a coupon' ).click(); + await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page.getByLabel( 'Enter code' ).fill( 'single-use-coupon' ); await page.getByRole( 'button', { name: 'Apply' } ).click(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-notices.shopper.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-notices.shopper.block_theme.side_effects.spec.ts index 7b986cb80fc59..48c5e901fcf89 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-notices.shopper.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-notices.shopper.block_theme.side_effects.spec.ts @@ -33,20 +33,10 @@ test.describe( 'Shopper → Notice Templates', () => { `npm run wp-env run tests-cli -- wp option update woocommerce_cart_page_id ${ cartShortcodeID }` ); - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); } ); - test.afterEach( async ( { wpCliUtils, frontendUtils } ) => { - const cartID = await wpCliUtils.getPostIDByTitle( 'Cart Shortcode' ); - await cli( - `npm run wp-env run tests-cli -- wp option update woocommerce_cart_page_id ${ cartID }` - ); - - await frontendUtils.emptyCart(); - } ); - test( 'default block notice templates are visible', async ( { frontendUtils, page, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-notices.shopper.classic_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-notices.shopper.classic_theme.spec.ts index ace75949ec177..8e18e92878683 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-notices.shopper.classic_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-notices.shopper.classic_theme.spec.ts @@ -34,20 +34,10 @@ test.describe( 'Shopper → Notice Templates', () => { `npm run wp-env run tests-cli -- wp option update woocommerce_cart_page_id ${ cartShortcodeID }` ); - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); } ); - test.afterEach( async ( { wpCliUtils, frontendUtils } ) => { - const cartID = await wpCliUtils.getPostIDByTitle( 'Cart Shortcode' ); - await cli( - `npm run wp-env run tests-cli -- wp option update woocommerce_cart_page_id ${ cartID }` - ); - - await frontendUtils.emptyCart(); - } ); - test( 'default classic notice templates are visible', async ( { frontendUtils, page, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-shipping.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-shipping.block_theme.side_effects.spec.ts index 16edb5dbecb3c..1b3509b5829f7 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-shipping.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-shipping.block_theme.side_effects.spec.ts @@ -2,7 +2,7 @@ * External dependencies */ import { expect, test as base } from '@woocommerce/e2e-playwright-utils'; -import { guestFile } from '@woocommerce/e2e-utils'; +import { FrontendUtils } from '@woocommerce/e2e-utils'; /** * Internal dependencies @@ -41,42 +41,52 @@ test.describe( 'Merchant → Shipping', () => { } ); test.describe( 'Shopper → Shipping', () => { - test.use( { storageState: guestFile } ); + test.beforeEach( async ( { shippingUtils } ) => { + await shippingUtils.enableShippingCostsRequireAddress(); + } ); test( 'Guest user can see shipping calculator on cart page', async ( { - frontendUtils, - page, + requestUtils, + browser, } ) => { - await frontendUtils.emptyCart(); - await frontendUtils.goToShop(); - await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); - await frontendUtils.goToCart(); + const guestContext = await browser.newContext(); + const userPage = await guestContext.newPage(); + + const userFrontendUtils = new FrontendUtils( userPage, requestUtils ); + + await userFrontendUtils.goToShop(); + await userFrontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); + await userFrontendUtils.goToCart(); await expect( - page.getByLabel( 'Add an address for shipping options' ) + userPage.getByLabel( 'Add an address for shipping options' ) ).toBeVisible(); } ); test( 'Guest user does not see shipping rates until full address is entered', async ( { - checkoutPageObject, - frontendUtils, - page, + requestUtils, + browser, } ) => { - await frontendUtils.emptyCart(); - await frontendUtils.goToShop(); - await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); - await frontendUtils.goToCheckout(); + const guestContext = await browser.newContext(); + const userPage = await guestContext.newPage(); + + const userFrontendUtils = new FrontendUtils( userPage, requestUtils ); + const userCheckoutPageObject = new CheckoutPage( { page: userPage } ); + + await userFrontendUtils.goToShop(); + await userFrontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); + await userFrontendUtils.goToCheckout(); await expect( - page.getByText( + userPage.getByText( 'Shipping options will be displayed here after entering your full shipping addres' ) ).toBeVisible(); - await checkoutPageObject.fillInCheckoutWithTestData(); + await userCheckoutPageObject.fillInCheckoutWithTestData(); await expect( - page.getByText( + userPage.getByText( 'Shipping options will be displayed here after entering your full shipping addres' ) ).toBeHidden(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-translations.shopper.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-translations.shopper.block_theme.side_effects.spec.ts index 1221d020dc134..5ce842e2f0a0e 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-translations.shopper.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/cart/cart-checkout-block-translations.shopper.block_theme.side_effects.spec.ts @@ -19,22 +19,10 @@ const test = base.extend< { checkoutPageObject: CheckoutPage } >( { } ); test.describe( 'Shopper → Translations', () => { - test.beforeAll( async () => { - await cli( - `npm run wp-env run tests-cli -- wp language core install nl_NL` - ); + test.beforeEach( async () => { await cli( `npm run wp-env run tests-cli -- wp site switch-language nl_NL` ); - await cli( - `npm run wp-env run tests-cli -- wp language plugin install woocommerce nl_NL` - ); - } ); - - test.afterAll( async () => { - await cli( - `npm run wp-env run tests-cli -- wp site switch-language en_US` - ); } ); test( 'User can view translated Cart block', async ( { @@ -65,7 +53,7 @@ test.describe( 'Shopper → Translations', () => { await expect( page.getByText( 'Totalen winkelwagen' ) ).toBeVisible(); await expect( - page.getByLabel( 'Een waardebon toevoegen' ) + page.getByRole( 'button', { name: 'Een waardebon toevoegen' } ) ).toBeVisible(); await expect( @@ -126,7 +114,7 @@ test.describe( 'Shopper → Translations', () => { await expect( page.getByText( 'Subtotaal' ) ).toBeVisible(); - await expect( page.getByText( 'Verzendmethoden' ) ).toBeVisible(); + await expect( page.getByText( 'Verzending' ) ).toBeVisible(); await expect( page.getByText( 'Totaal', { exact: true } ) diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/cart/update-price-plugin.php b/plugins/woocommerce-blocks/tests/e2e/tests/cart/update-price-plugin.php deleted file mode 100644 index d008b9e601009..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/tests/cart/update-price-plugin.php +++ /dev/null @@ -1,19 +0,0 @@ -get_cart() as $hash => $value ) { - $value['data']->set_price( 50 ); - } -} - -add_action( 'woocommerce_before_calculate_totals', 'calc_price' ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/catalog-sorting/catalog-sorting.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/catalog-sorting/catalog-sorting.block_theme.spec.ts new file mode 100644 index 0000000000000..86cbe18f24744 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/catalog-sorting/catalog-sorting.block_theme.spec.ts @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Catalog Sorting', + slug: 'woocommerce/catalog-sorting', + class: '.wc-block-catalog-sorting', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( "block can't be inserted in Post Editor", async ( { + editor, + admin, + } ) => { + await admin.createNewPost(); + await expect( + editor.insertBlock( { name: blockData.slug } ) + ).rejects.toThrow( + new RegExp( `Block type '${ blockData.slug }' is not registered.` ) + ); + } ); + + test( 'block should be already added in the Product Catalog Template', async ( { + editorUtils, + admin, + } ) => { + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//archive-product', + postType: 'wp_template', + } ); + await editorUtils.enterEditMode(); + const alreadyPresentBlock = await editorUtils.getBlockByName( + blockData.slug + ); + + await expect( alreadyPresentBlock ).toHaveText( 'Default sorting' ); + } ); + + test( 'block can be inserted in the Site Editor', async ( { + admin, + requestUtils, + editorUtils, + editor, + } ) => { + const template = await requestUtils.createTemplate( 'wp_template', { + slug: 'sorter', + title: 'Sorter', + content: 'howdy', + } ); + + await admin.visitSiteEditor( { + postId: template.id, + postType: 'wp_template', + } ); + + await editorUtils.enterEditMode(); + + await editor.insertBlock( { + name: blockData.slug, + } ); + + const block = await editorUtils.getBlockByName( blockData.slug ); + await expect( block ).toHaveText( 'Default sorting' ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.guest-shopper.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.guest-shopper.block_theme.side_effects.spec.ts index 39af6a676c613..54ffad7f4be31 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.guest-shopper.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.guest-shopper.block_theme.side_effects.spec.ts @@ -3,10 +3,6 @@ */ import { expect, test as base } from '@woocommerce/e2e-playwright-utils'; import { guestFile } from '@woocommerce/e2e-utils'; -import { - installPluginFromPHPFile, - uninstallPluginFromPHPFile, -} from '@woocommerce/e2e-mocks/custom-plugins'; /** * Internal dependencies @@ -26,19 +22,12 @@ const test = base.extend< { checkoutPageObject: CheckoutPage } >( { test.describe( 'Shopper → Additional Checkout Fields', () => { test.describe( 'Guest shopper', () => { test.use( { storageState: guestFile } ); - test.beforeAll( async () => { - await installPluginFromPHPFile( - `${ __dirname }/additional-checkout-fields-plugin.php` - ); - } ); - test.afterAll( async () => { - await uninstallPluginFromPHPFile( - `${ __dirname }/additional-checkout-fields-plugin.php` + + test.beforeEach( async ( { frontendUtils, requestUtils } ) => { + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-additional-checkout-fields' ); - } ); - test.beforeEach( async ( { frontendUtils } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToCheckout(); @@ -55,7 +44,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { { contact: { 'Enter a gift message to include in the package': - 'This is for you!', + 'For my non-ascii named friend: niño', }, address: { shipping: { @@ -67,7 +56,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { 'Confirm government ID': '54321', }, }, - additional: { + order: { 'How did you hear about us?': 'Other', 'What is your favourite colour?': 'Blue', }, @@ -105,7 +94,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { { contact: { 'Enter a gift message to include in the package': - 'This is for you!', + 'For my non-ascii named friend: niño', 'Is this a personal purchase or a business purchase?': 'business', }, @@ -119,7 +108,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { 'Confirm government ID': '54321', }, }, - additional: { + order: { 'How did you hear about us?': 'Other', 'What is your favourite colour?': 'Blue', }, @@ -188,7 +177,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { [ 'What is your favourite colour?', 'Blue' ], [ 'Enter a gift message to include in the package', - 'This is for you!', + 'For my non-ascii named friend: niño', ], [ 'Do you want to subscribe to our newsletter?', 'Yes' ], [ 'Would you like a free gift with your order?', 'Yes' ], @@ -220,7 +209,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { .getByLabel( 'Enter a gift message to include in the package' ) - ).toHaveValue( 'This is for you!' ); + ).toHaveValue( 'For my non-ascii named friend: niño' ); await expect( checkoutPageObject.page .getByRole( 'group', { diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.merchant.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.merchant.block_theme.side_effects.spec.ts index 2a3ce45c4959f..2bfb45a8ac068 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.merchant.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.merchant.block_theme.side_effects.spec.ts @@ -2,10 +2,6 @@ * External dependencies */ import { expect, test as base } from '@woocommerce/e2e-playwright-utils'; -import { - installPluginFromPHPFile, - uninstallPluginFromPHPFile, -} from '@woocommerce/e2e-mocks/custom-plugins'; /** * Internal dependencies @@ -23,19 +19,11 @@ const test = base.extend< { checkoutPageObject: CheckoutPage } >( { } ); test.describe( 'Merchant → Additional Checkout Fields', () => { - test.beforeAll( async () => { - await installPluginFromPHPFile( - `${ __dirname }/additional-checkout-fields-plugin.php` + test.beforeEach( async ( { requestUtils, frontendUtils } ) => { + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-additional-checkout-fields' ); - } ); - test.afterAll( async () => { - await uninstallPluginFromPHPFile( - `${ __dirname }/additional-checkout-fields-plugin.php` - ); - } ); - test.beforeEach( async ( { frontendUtils } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToCheckout(); @@ -67,7 +55,7 @@ test.describe( 'Merchant → Additional Checkout Fields', () => { 'Confirm government ID': '54321', }, }, - additional: { + order: { 'How did you hear about us?': 'Other', 'What is your favourite colour?': 'Blue', }, @@ -203,7 +191,7 @@ test.describe( 'Merchant → Additional Checkout Fields', () => { 'Confirm government ID': '54321', }, }, - additional: { + order: { 'How did you hear about us?': 'Other', 'What is your favourite colour?': 'Blue', }, @@ -283,7 +271,9 @@ test.describe( 'Merchant → Additional Checkout Fields', () => { // Use Locator here because the select2 box is duplicated in shipping. await admin.page - .locator( '[id="\\/billing\\/first-plugin-namespace\\/road-size"]' ) + .locator( + '[id="\\_wc_billing\\/first-plugin-namespace\\/road-size"]' + ) .selectOption( 'wide' ); // Handle changing the contact fields. @@ -335,7 +325,7 @@ test.describe( 'Merchant → Additional Checkout Fields', () => { // Use Locator here because the select2 box is duplicated in billing. await admin.page .locator( - '[id="\\/shipping\\/first-plugin-namespace\\/road-size"]' + '[id="\\_wc_shipping\\/first-plugin-namespace\\/road-size"]' ) .selectOption( 'super-wide' ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.shopper.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.shopper.block_theme.side_effects.spec.ts index 585cb8c8a137b..5e488d4d198ff 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.shopper.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/additional-fields.shopper.block_theme.side_effects.spec.ts @@ -3,10 +3,6 @@ */ import { expect, test as base } from '@woocommerce/e2e-playwright-utils'; import { customerFile } from '@woocommerce/e2e-utils'; -import { - installPluginFromPHPFile, - uninstallPluginFromPHPFile, -} from '@woocommerce/e2e-mocks/custom-plugins'; /** * Internal dependencies @@ -26,19 +22,12 @@ const test = base.extend< { checkoutPageObject: CheckoutPage } >( { test.describe( 'Shopper → Additional Checkout Fields', () => { test.describe( 'Logged in shopper', () => { test.use( { storageState: customerFile } ); - test.beforeAll( async () => { - await installPluginFromPHPFile( - `${ __dirname }/additional-checkout-fields-plugin.php` - ); - } ); - test.afterAll( async () => { - await uninstallPluginFromPHPFile( - `${ __dirname }/additional-checkout-fields-plugin.php` + + test.beforeEach( async ( { requestUtils, frontendUtils } ) => { + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-additional-checkout-fields' ); - } ); - test.beforeEach( async ( { frontendUtils } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToCheckout(); @@ -70,7 +59,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { 'Confirm government ID': '54321', }, }, - additional: { + order: { 'How did you hear about us?': 'Other', 'What is your favourite colour?': 'Blue', }, @@ -258,7 +247,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { 'Confirm government ID': '54321', }, }, - additional: { + order: { 'How did you hear about us?': 'Other', 'What is your favourite colour?': 'Blue', }, @@ -351,7 +340,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { 'Confirm government ID': '43210', }, }, - additional: { + order: { 'What is your favourite colour?': 'Red', }, } @@ -442,7 +431,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { 'Confirm government ID': '543 21', }, }, - additional: { + order: { 'How did you hear about us?': 'Other', 'What is your favourite colour?': 'Blue', }, @@ -629,7 +618,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { 'Confirm government ID': '12345', }, }, - additional: { 'How did you hear about us?': 'Other' }, + order: { 'How did you hear about us?': 'Other' }, } ); @@ -682,7 +671,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { 'Confirm government ID': '54321', }, }, - additional: { 'How did you hear about us?': 'Other' }, + order: { 'How did you hear about us?': 'Other' }, } ); @@ -743,7 +732,7 @@ test.describe( 'Shopper → Additional Checkout Fields', () => { 'Confirm government ID': '54321', }, }, - additional: { 'How did you hear about us?': 'Other' }, + order: { 'How did you hear about us?': 'Other' }, } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.merchant.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.merchant.block_theme.spec.ts index a71e0c0a207d9..e60d18fb2706f 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.merchant.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.merchant.block_theme.spec.ts @@ -78,7 +78,7 @@ test.describe( 'Merchant → Checkout', () => { } ); test.describe( 'Can adjust T&S and Privacy Policy options', () => { - test.beforeAll( async ( { browser } ) => { + test.beforeEach( async ( { browser } ) => { const page = await browser.newPage(); await page.goto( `${ process.env.WORDPRESS_BASE_URL }/?setup_terms_and_privacy` @@ -89,17 +89,6 @@ test.describe( 'Merchant → Checkout', () => { await page.close(); } ); - test.afterAll( async ( { browser } ) => { - const page = await browser.newPage(); - await page.goto( - `${ process.env.WORDPRESS_BASE_URL }/?teardown_terms_and_privacy` - ); - await expect( - page.getByText( 'Terms & Privacy pages teared down.' ) - ).toBeVisible(); - await page.close(); - } ); - test( 'Merchant can see T&S and Privacy Policy links without checkbox', async ( { frontendUtils, checkoutPageObject, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.shopper.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.shopper.block_theme.side_effects.spec.ts index b825f4bdffe61..3f45ce189536d 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.shopper.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout-block.shopper.block_theme.side_effects.spec.ts @@ -43,7 +43,7 @@ const blockData: BlockData = { test.describe( 'Shopper → Account (guest user)', () => { test.use( { storageState: guestFile } ); - test.beforeAll( async ( { requestUtils } ) => { + test.beforeEach( async ( { requestUtils, frontendUtils } ) => { await requestUtils.rest( { method: 'PUT', path: 'wc/v3/settings/account/woocommerce_enable_guest_checkout', @@ -54,9 +54,7 @@ test.describe( 'Shopper → Account (guest user)', () => { path: 'wc/v3/settings/account/woocommerce_enable_checkout_login_reminder', data: { value: 'yes' }, } ); - } ); - test.beforeEach( async ( { frontendUtils } ) => { - await frontendUtils.emptyCart(); + await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToCheckout(); @@ -129,32 +127,16 @@ test.describe( 'Shopper → Local pickup', () => { .click(); } ); - test.afterEach( async ( { admin } ) => { - // Enable local pickup. - await admin.visitAdminPage( - 'admin.php', - 'page=wc-settings&tab=shipping§ion=pickup_location' - ); - await admin.page.getByRole( 'button', { name: 'Edit' } ).last().click(); - await admin.page - .getByRole( 'button', { name: 'Delete location' } ) - .click(); - await admin.page - .getByRole( 'button', { name: 'Save changes' } ) - .click(); - } ); - test( 'The shopper can choose a local pickup option', async ( { page, frontendUtils, checkoutPageObject, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCheckout(); - await page.getByRole( 'radio', { name: 'Local Pickup free' } ).click(); + await page.getByRole( 'radio', { name: 'Pickup' } ).click(); await expect( page.getByLabel( 'Testing' ).last() ).toBeVisible(); await page.getByLabel( 'Testing' ).last().check(); @@ -172,12 +154,13 @@ test.describe( 'Shopper → Local pickup', () => { frontendUtils, checkoutPageObject, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCheckout(); - await page.getByRole( 'radio', { name: 'Local Pickup free' } ).click(); + await page + .getByRole( 'radio', { name: 'Pickup', exact: true } ) + .click(); await page .getByLabel( 'Email address' ) .fill( 'thisShouldRemainHere@mail.com' ); @@ -185,19 +168,21 @@ test.describe( 'Shopper → Local pickup', () => { 'thisShouldRemainHere@mail.com' ); - await page.getByRole( 'radio', { name: 'Shipping from free' } ).click(); + await page.getByRole( 'radio', { name: 'Ship', exact: true } ).click(); await expect( page.getByLabel( 'Email address' ) ).toHaveValue( 'thisShouldRemainHere@mail.com' ); await checkoutPageObject.fillInCheckoutWithTestData(); - await page.getByRole( 'radio', { name: 'Local Pickup free' } ).click(); + await page + .getByRole( 'radio', { name: 'Pickup', exact: true } ) + .click(); await expect( page.getByLabel( 'Email address' ) ).toHaveValue( 'john.doe@test.com' ); - await page.getByRole( 'radio', { name: 'Shipping from free' } ).click(); + await page.getByRole( 'radio', { name: 'Ship', exact: true } ).click(); await expect( page.getByLabel( 'Email address' ) ).toHaveValue( 'john.doe@test.com' ); @@ -209,7 +194,6 @@ test.describe( 'Shopper → Payment Methods', () => { frontendUtils, page, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCheckout(); @@ -257,62 +241,31 @@ test.describe( 'Shopper → Shipping and Billing Addresses', () => { // `as string` is safe here because we know the variable is a string, it is defined above. const blockSelectorInEditor = blockData.selectors.editor.block as string; - test.beforeEach( - async ( { editor, frontendUtils, admin, editorUtils, page } ) => { - await admin.visitSiteEditor( { - postId: 'woocommerce/woocommerce//page-checkout', - postType: 'wp_template', - } ); - await editorUtils.enterEditMode(); - await editor.openDocumentSettingsSidebar(); - await editor.selectBlocks( - blockSelectorInEditor + - ' [data-type="woocommerce/checkout-shipping-address-block"]' - ); - - const checkbox = page.getByRole( 'checkbox', { - name: 'Company', - exact: true, - } ); - await checkbox.check(); - await expect( checkbox ).toBeChecked(); - await expect( - editor.canvas.locator( - 'div.wc-block-components-address-form__company' - ) - ).toBeVisible(); - await editorUtils.saveSiteEditorEntities(); - await frontendUtils.emptyCart(); - } - ); - - test.afterEach( - async ( { frontendUtils, admin, editorUtils, editor, page } ) => { - await frontendUtils.emptyCart(); - await admin.visitSiteEditor( { - postId: 'woocommerce/woocommerce//page-checkout', - postType: 'wp_template', - } ); - await editorUtils.enterEditMode(); - await editor.openDocumentSettingsSidebar(); - await editor.selectBlocks( - blockSelectorInEditor + - ' [data-type="woocommerce/checkout-shipping-address-block"]' - ); - const checkbox = page.getByRole( 'checkbox', { - name: 'Company', - exact: true, - } ); - await checkbox.uncheck(); - await expect( checkbox ).not.toBeChecked(); - await expect( - editor.canvas.locator( - '.wc-block-checkout__shipping-fields .wc-block-components-address-form__company' - ) - ).toBeHidden(); - await editorUtils.saveSiteEditorEntities(); - } - ); + test.beforeEach( async ( { editor, admin, editorUtils, page } ) => { + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//page-checkout', + postType: 'wp_template', + } ); + await editorUtils.enterEditMode(); + await editor.openDocumentSettingsSidebar(); + await editor.selectBlocks( + blockSelectorInEditor + + ' [data-type="woocommerce/checkout-shipping-address-block"]' + ); + + const checkbox = page.getByRole( 'checkbox', { + name: 'Company', + exact: true, + } ); + await checkbox.check(); + await expect( checkbox ).toBeChecked(); + await expect( + editor.canvas.locator( + 'div.wc-block-components-address-form__company' + ) + ).toBeVisible(); + await editorUtils.saveSiteEditorEntities(); + } ); test( 'User can add postcodes for different countries', async ( { frontendUtils, @@ -342,7 +295,6 @@ test.describe( 'Shopper → Shipping (customer user)', () => { page, } ) => { await frontendUtils.goToShop(); - await frontendUtils.emptyCart(); await frontendUtils.addToCart( 'Beanie' ); await frontendUtils.goToCheckout(); await expect( @@ -414,7 +366,6 @@ test.describe( 'Shopper → Place Guest Order', () => { frontendUtils, page, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCheckout(); @@ -433,7 +384,7 @@ test.describe( 'Shopper → Place Guest Order', () => { } ); test.describe( 'Shopper → Place Virtual Order', () => { - test.beforeAll( async ( { requestUtils } ) => { + test.beforeEach( async ( { requestUtils } ) => { await requestUtils.rest( { method: 'PUT', path: 'wc/v3/settings/general/woocommerce_ship_to_countries', @@ -441,14 +392,6 @@ test.describe( 'Shopper → Place Virtual Order', () => { } ); } ); - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.rest( { - method: 'PUT', - path: 'wc/v3/settings/general/woocommerce_ship_to_countries', - data: { value: 'all' }, - } ); - } ); - test( 'can place a digital order when shipping is disabled', async ( { checkoutPageObject, frontendUtils, @@ -457,7 +400,6 @@ test.describe( 'Shopper → Place Virtual Order', () => { } ) => { await localPickupUtils.disableLocalPickup(); - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCart(); @@ -490,7 +432,6 @@ test.describe( 'Shopper → Place Virtual Order', () => { } ) => { await localPickupUtils.enableLocalPickup(); - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCart(); @@ -521,7 +462,6 @@ test.describe( 'Shopper → Checkout Form Errors (guest user)', () => { frontendUtils, page, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCheckout(); @@ -554,36 +494,40 @@ test.describe( 'Shopper → Checkout Form Errors (guest user)', () => { test.describe( 'Billing Address Form', () => { const blockSelectorInEditor = blockData.selectors.editor.block as string; - test( 'Enable company field', async ( { editor, admin, editorUtils } ) => { + test( 'Enable company field', async ( { + page, + editor, + admin, + editorUtils, + } ) => { await admin.visitSiteEditor( { postId: 'woocommerce/woocommerce//page-checkout', postType: 'wp_template', + canvas: 'edit', } ); - await editorUtils.enterEditMode(); + await editor.openDocumentSettingsSidebar(); + await editor.selectBlocks( blockSelectorInEditor + ' [data-type="woocommerce/checkout-shipping-address-block"]' ); - const checkbox = editor.page.getByRole( 'checkbox', { - name: 'Company', + const companyCheckbox = page.getByLabel( 'Company', { exact: true, } ); - await checkbox.check(); - await expect( checkbox ).toBeChecked(); - await expect( - editor.canvas.locator( - 'div.wc-block-components-address-form__company' - ) - ).toBeVisible(); + await companyCheckbox.check(); + await expect( companyCheckbox ).toBeChecked(); + + const companyInput = editor.canvas.getByLabel( 'Company (optional)' ); + await expect( companyInput ).toBeVisible(); + await editorUtils.saveSiteEditorEntities(); } ); const shippingTestData = { firstname: 'John', lastname: 'Doe', - company: 'Automattic', addressfirstline: '123 Easy Street', addresssecondline: 'Testville', country: 'United States (US)', @@ -595,7 +539,6 @@ test.describe( 'Billing Address Form', () => { const billingTestData = { first_name: '', last_name: '', - company: '', address_1: '', address_2: '', country: 'United States (US)', @@ -613,10 +556,10 @@ test.describe( 'Billing Address Form', () => { page, checkoutPageObject, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCheckout(); + await checkoutPageObject.fillShippingDetails( shippingTestData ); await page.getByLabel( 'Use same address for billing' ).uncheck(); @@ -675,10 +618,6 @@ test.describe( 'Billing Address Form', () => { page.locator( '#billing-state input' ) ).toHaveValue( value ); break; - default: - await expect( - page.locator( `#billing-${ key }` ) - ).toHaveValue( value ); } } } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout.page.ts b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout.page.ts index a936c25a6bd59..31716c4f1fee6 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout.page.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/checkout.page.ts @@ -60,10 +60,10 @@ export class CheckoutPage { billing?: Record< string, string >; }; contact?: Record< string, string >; - additional?: Record< string, string >; + order?: Record< string, string >; } = { address: { shipping: {}, billing: {} }, - additional: {}, + order: {}, contact: {}, } ) { @@ -100,11 +100,11 @@ export class CheckoutPage { } if ( - typeof additionalFields.additional !== 'undefined' && - Object.keys( additionalFields.additional ).length > 0 + typeof additionalFields.order !== 'undefined' && + Object.keys( additionalFields.order ).length > 0 ) { await this.fillAdditionalInformationSection( - additionalFields.additional + additionalFields.order ); } diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/order-confirmation.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/order-confirmation.block_theme.side_effects.spec.ts index feaa1a9f7fb93..a343c376989b8 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/checkout/order-confirmation.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/checkout/order-confirmation.block_theme.side_effects.spec.ts @@ -49,12 +49,7 @@ test.describe( 'Shopper → Order Confirmation (logged in user)', () => { await editorUtils.transformIntoBlocks(); } ); - test.afterEach( async ( { localPickupUtils } ) => { - await localPickupUtils.enableLocalPickup(); - } ); - test( 'Place order', async ( { frontendUtils, pageObject, page } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.addToCart( SIMPLE_VIRTUAL_PRODUCT_NAME ); @@ -131,7 +126,6 @@ test.describe( 'Shopper → Order Confirmation (guest user)', () => { 'User is not logged out' ).toBeVisible(); - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCheckout(); @@ -185,9 +179,7 @@ test.describe( 'Shopper → Order Confirmation → Local Pickup', () => { await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_PHYSICAL_PRODUCT_NAME ); await frontendUtils.goToCheckout(); - await pageObject.page - .getByRole( 'radio', { name: 'Local Pickup free' } ) - .click(); + await pageObject.page.getByRole( 'radio', { name: 'Pickup' } ).click(); await pageObject.fillInCheckoutWithTestData(); await pageObject.placeOrder(); await expect( @@ -203,7 +195,6 @@ test.describe( 'Shopper → Order Confirmation → Downloadable Products', () => let confirmationPageUrl: string; test.beforeEach( async ( { frontendUtils, pageObject } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( SIMPLE_VIRTUAL_PRODUCT_NAME ); await frontendUtils.goToCheckout(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/classic-template/classic-template.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/classic-template/classic-template.block_theme.side_effects.spec.ts index 8757abfcc34b4..63898037d99a1 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/classic-template/classic-template.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/classic-template/classic-template.block_theme.side_effects.spec.ts @@ -4,7 +4,6 @@ import { BlockData } from '@woocommerce/e2e-types'; import { test, expect } from '@woocommerce/e2e-playwright-utils'; import { cli } from '@woocommerce/e2e-utils'; -import { deleteAllTemplates } from '@wordpress/e2e-test-utils'; /** * Internal dependencies @@ -14,95 +13,94 @@ const blockData: Partial< BlockData > = { name: 'woocommerce/legacy-template', }; -const templates = { - 'single-product': { - templateTitle: 'Single Product', +const templates = [ + { + title: 'Single Product', slug: 'single-product', - frontendPage: '/product/single/', + path: '/product/single/', }, - // This test is disabled because archives are disabled for attributes by default. This can be uncommented when this is toggled on. - //'taxonomy-product_attribute': { - // templateTitle: 'Product Attribute', + // This test is disabled because archives are disabled for attributes by + // default. This can be uncommented when this is toggled on. + //{ + // title: 'Product Attribute', // slug: 'taxonomy-product_attribute', - // frontendPage: '/product-attribute/color/', + // path: '/product-attribute/color/', //}, - 'taxonomy-product_cat': { - templateTitle: 'Product Category', + { + title: 'Product Category', slug: 'taxonomy-product_cat', - frontendPage: '/product-category/music/', + path: '/product-category/music/', }, - // We don't have products with tags in the test site. Uncomment this when we have products with tags. - // 'taxonomy-product_tag': { - // templateTitle: 'Product Tag', - // slug: 'taxonomy-product_tag', - // frontendPage: '/product-tag/hoodie/', - // }, - 'archive-product': { - templateTitle: 'Product Catalog', + { + title: 'Product Tag', + slug: 'taxonomy-product_tag', + path: '/product-tag/recommended/', + }, + { + title: 'Product Catalog', slug: 'archive-product', - frontendPage: '/shop/', + path: '/shop/', }, - 'product-search-results': { - templateTitle: 'Product Search Results', + { + title: 'Product Search Results', slug: 'product-search-results', - frontendPage: '/?s=s&post_type=product', + path: '/?s=s&post_type=product', }, -}; - -test.beforeAll( async () => { - await cli( - 'npm run wp-env run tests-cli -- wp option update wc_blocks_use_blockified_product_grid_block_as_template false' - ); - await deleteAllTemplates( 'wp_template' ); -} ); +]; -test.afterAll( async () => { - await cli( - 'npm run wp-env run tests-cli -- wp option delete wc_blocks_use_blockified_product_grid_block_as_template' - ); - await deleteAllTemplates( 'wp_template' ); -} ); +test.describe( `${ blockData.name } Block `, () => { + test.beforeEach( async () => { + await cli( + 'npm run wp-env run tests-cli -- wp option update wc_blocks_use_blockified_product_grid_block_as_template false' + ); + } ); -for ( const { templateTitle, slug } of Object.values( templates ) ) { - test.describe( `${ blockData.name } Block `, () => { - test( `is rendered on ${ templateTitle } template`, async ( { + for ( const template of templates ) { + test( `is rendered on ${ template.title } template`, async ( { admin, - editorUtils, + editor, } ) => { await admin.visitSiteEditor( { - postId: `woocommerce/woocommerce//${ slug }`, + postId: `woocommerce/woocommerce//${ template.slug }`, postType: 'wp_template', + canvas: 'edit', } ); - await editorUtils.enterEditMode(); - const block = await editorUtils.getBlockByName( blockData.name ); + + const block = editor.canvas.locator( + `[data-type="${ blockData.name }"]` + ); await expect( block ).toBeVisible(); } ); - // These tests consistently fail due to the default content of the page--potentially the classic block is not being - // used after another test runs. Reenable this when we have a solution for this. + // These tests consistently fail due to the default content of the + // page--potentially the classic block is not being used after + // another test runs. Reenable this when we have a solution for + // this. // eslint-disable-next-line playwright/no-skipped-test - test.skip( `is rendered on ${ templateTitle } template - frontend side`, async ( { + test.skip( `is rendered on ${ template.title } template - frontend side`, async ( { admin, editor, - editorUtils, page, } ) => { await admin.visitSiteEditor( { - postId: `woocommerce/woocommerce//${ slug }`, + postId: `woocommerce/woocommerce//${ template.slug }`, postType: 'wp_template', + canvas: 'edit', } ); - await editorUtils.enterEditMode(); + await editor.insertBlock( { name: 'core/paragraph', attributes: { content: 'Hello World' }, } ); + await editor.saveSiteEditorEntities(); - await page.goto( frontendPage ); + + await page.goto( template.path ); await expect( page.getByText( 'Hello World' ).first() ).toBeVisible(); } ); - } ); -} + } +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/featured-category/featured-category.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/featured-category/featured-category.block_theme.spec.ts new file mode 100644 index 0000000000000..91ec40f0d1469 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/featured-category/featured-category.block_theme.spec.ts @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + slug: 'woocommerce/featured-category', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await blockLocator.getByText( 'Music' ).click(); + await blockLocator.getByText( 'Done' ).click(); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( blockLocatorFrontend ).toBeVisible(); + await expect( blockLocatorFrontend.getByText( 'Music' ) ).toBeVisible(); + await expect( + blockLocatorFrontend.getByText( 'Shop now' ) + ).toBeVisible(); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/featured-product/featured-product.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/featured-product/featured-product.block_theme.spec.ts new file mode 100644 index 0000000000000..a2fda183febac --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/featured-product/featured-product.block_theme.spec.ts @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + slug: 'woocommerce/featured-product', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await blockLocator.getByText( 'Album' ).click(); + await blockLocator.getByText( 'Done' ).click(); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( blockLocatorFrontend ).toBeVisible(); + await expect( blockLocatorFrontend.getByText( 'Album' ) ).toBeVisible(); + await expect( + blockLocatorFrontend.getByText( 'Shop now' ) + ).toBeVisible(); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/active-filters.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/active-filters.block_theme.spec.ts index 796afcba86e28..2b654b721916a 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/active-filters.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/active-filters.block_theme.spec.ts @@ -22,7 +22,7 @@ const test = base.extend< { }, } ); -test.describe( 'Product Filter: Active Filters Block', async () => { +test.describe( 'Product Filter: Active Filters Block', () => { test.describe( 'frontend', () => { test( 'Without any filters selected, only a wrapper block is rendered', async ( { page, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/attribute-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/attribute-filter.block_theme.spec.ts index 1901a407cbec6..32e7fd1af8b58 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/attribute-filter.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/attribute-filter.block_theme.spec.ts @@ -70,7 +70,7 @@ const test = base.extend< { }, } ); -test.describe( 'Product Filter: Attribute Block', async () => { +test.describe( 'Product Filter: Attribute Block', () => { test.describe( 'With default display style', () => { test.describe( 'With show counts enabled', () => { test( 'Renders checkboxes with associated product counts', async ( { diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/basic.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/basic.block_theme.spec.ts index f4b182c392ca5..9192a97daef21 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/basic.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/basic.block_theme.spec.ts @@ -31,7 +31,7 @@ const filterBlocks = [ }, ]; -test.describe( 'Filter blocks registration', async () => { +test.describe( 'Filter blocks registration', () => { test.beforeEach( async ( { admin } ) => { await admin.createNewPost(); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/price-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/price-filter.block_theme.spec.ts index 5de113c3efc59..3a34b3c063c90 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/price-filter.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/price-filter.block_theme.spec.ts @@ -22,7 +22,7 @@ const test = base.extend< { }, } ); -test.describe( 'Product Filter: Price Filter Block', async () => { +test.describe( 'Product Filter: Price Filter Block', () => { test.describe( 'frontend', () => { test( 'With price filters applied it shows the correct price', async ( { page, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/rating-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/rating-filter.block_theme.spec.ts index 3f037eaa7309c..14e25b38d7c78 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/rating-filter.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/rating-filter.block_theme.spec.ts @@ -22,7 +22,7 @@ const test = base.extend< { }, } ); -test.describe( 'Product Filter: Rating Filter Block', async () => { +test.describe( 'Product Filter: Rating Filter Block', () => { test.describe( 'frontend', () => { test( 'Renders a checkbox list with the available ratings', async ( { page, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/stock-status.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/stock-status.block_theme.spec.ts index 0d6112fa110b7..f5ab3553abe4b 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/stock-status.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/filter-blocks/stock-status.block_theme.spec.ts @@ -38,7 +38,7 @@ const test = base.extend< { }, } ); -test.describe( 'Product Filter: Stock Status Block', async () => { +test.describe( 'Product Filter: Stock Status Block', () => { test.describe( 'With default display style', () => { test( 'renders a checkbox list with the available stock statuses', async ( { page, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/handpicked-products/handpicked-products.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/handpicked-products/handpicked-products.block_theme.spec.ts new file mode 100644 index 0000000000000..b5545dc7ebe3f --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/handpicked-products/handpicked-products.block_theme.spec.ts @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Hand-picked Products', + slug: 'woocommerce/handpicked-products', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await blockLocator.getByText( 'Album' ).click(); + await blockLocator.getByText( 'Done' ).click(); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( blockLocatorFrontend ).toBeVisible(); + await expect( blockLocatorFrontend.getByText( 'Album' ) ).toBeVisible(); + await expect( + blockLocatorFrontend.getByText( 'Add to Cart' ) + ).toBeVisible(); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/local-pickup/local-pickup.merchant.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/local-pickup/local-pickup.merchant.block_theme.side_effects.spec.ts index d7bbacf3484d3..dc43f1d400731 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/local-pickup/local-pickup.merchant.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/local-pickup/local-pickup.merchant.block_theme.side_effects.spec.ts @@ -25,11 +25,6 @@ test.describe( 'Merchant → Local Pickup Settings', () => { await localPickupUtils.enableLocalPickup(); } ); - test.afterEach( async ( { localPickupUtils } ) => { - await localPickupUtils.deleteLocations(); - await localPickupUtils.setLocalPickupTitle( 'Local Pickup' ); - } ); - test( 'Updating the title in WC Settings updates the local pickup text in the block and vice/versa', async ( { page, localPickupUtils, @@ -49,7 +44,7 @@ test.describe( 'Merchant → Local Pickup Settings', () => { 'woocommerce/checkout-shipping-method-block' ); await editor.selectBlocks( block ); - const fakeInput = editor.canvas.getByLabel( 'Local Pickup' ); + const fakeInput = editor.canvas.getByLabel( 'Pickup', { exact: true } ); await fakeInput.click(); const isMacOS = process.platform === 'darwin'; // darwin is macOS @@ -64,6 +59,8 @@ test.describe( 'Merchant → Local Pickup Settings', () => { await fakeInput.pressSequentially( 'This is a test' ); await editor.canvas.getByText( 'This is a test' ).isVisible(); await editor.saveSiteEditorEntities(); + + // Now check if it's visible in the local pickup settings. await localPickupUtils.openLocalPickupSettings(); await expect( page.getByLabel( 'Title' ) ).toHaveValue( 'This is a test' diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/local-pickup/local-pickup.merchant.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/local-pickup/local-pickup.merchant.block_theme.spec.ts index eea830c11e399..4b3037152e8fc 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/local-pickup/local-pickup.merchant.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/local-pickup/local-pickup.merchant.block_theme.spec.ts @@ -24,23 +24,19 @@ test.describe( 'Merchant → Local Pickup Settings', () => { } ); test( 'user can change the title', async ( { page, localPickupUtils } ) => { - await page - .getByPlaceholder( 'Local Pickup' ) - .fill( 'Local Pickup Test #1' ); + await page.getByPlaceholder( 'Pickup' ).fill( 'Local Pickup Test #1' ); await localPickupUtils.saveLocalPickupSettings(); - await expect( page.getByPlaceholder( 'Local Pickup' ) ).toHaveValue( + await expect( page.getByPlaceholder( 'Pickup' ) ).toHaveValue( 'Local Pickup Test #1' ); - await page - .getByPlaceholder( 'Local Pickup' ) - .fill( 'Local Pickup Test #2' ); + await page.getByPlaceholder( 'Pickup' ).fill( 'Local Pickup Test #2' ); await localPickupUtils.saveLocalPickupSettings(); - await expect( page.getByPlaceholder( 'Local Pickup' ) ).toHaveValue( + await expect( page.getByPlaceholder( 'Pickup' ) ).toHaveValue( 'Local Pickup Test #2' ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart-block.merchant.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart-block.merchant.block_theme.side_effects.spec.ts index 0be8dfc60d4fe..b17d0665e3da4 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart-block.merchant.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart-block.merchant.block_theme.side_effects.spec.ts @@ -9,19 +9,16 @@ const blockData: BlockData = { slug: 'woocommerce/mini-cart', mainClass: '.wc-block-minicart', selectors: { + editor: { + block: '.wp-block-woocommerce-mini-cart', + insertButton: "//button//span[text()='Mini-Cart']", + }, frontend: {}, - editor: {}, }, }; test.describe( 'Merchant → Mini Cart', () => { test.describe( 'in FSE editor', () => { - test.afterAll( async ( { templateApiUtils } ) => { - await templateApiUtils.revertTemplate( - 'woocommerce/woocommerce//single-product' - ); - } ); - test( 'can be inserted in FSE area', async ( { editorUtils, editor, @@ -56,13 +53,10 @@ test.describe( 'Merchant → Mini Cart', () => { const miniCartButton = editorUtils.page.getByRole( 'option', { name: blockData.name, - exact: true, } ); - await expect( miniCartButton ).toHaveAttribute( - 'aria-disabled', - 'true' - ); + await expect( miniCartButton ).toBeVisible(); + await expect( miniCartButton ).toBeDisabled(); } ); } ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart-block.shopper.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart-block.shopper.block_theme.side_effects.spec.ts index fad9219e018ef..60b1395f6fe61 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart-block.shopper.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart-block.shopper.block_theme.side_effects.spec.ts @@ -10,22 +10,10 @@ import { cli } from '@woocommerce/e2e-utils'; import { REGULAR_PRICED_PRODUCT_NAME } from '../checkout/constants'; test.describe( 'Shopper → Translations', () => { - test.beforeAll( async () => { - await cli( - `npm run wp-env run tests-cli -- wp language core install nl_NL` - ); + test.beforeEach( async () => { await cli( `npm run wp-env run tests-cli -- wp site switch-language nl_NL` ); - await cli( - `npm run wp-env run tests-cli -- wp language plugin install woocommerce nl_NL` - ); - } ); - - test.afterAll( async () => { - await cli( - `npm run wp-env run tests-cli -- wp site switch-language en_US` - ); } ); test( 'User can see translation in empty Mini-Cart', async ( { @@ -33,7 +21,6 @@ test.describe( 'Shopper → Translations', () => { frontendUtils, miniCartUtils, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await miniCartUtils.openMiniCart(); @@ -70,7 +57,7 @@ test.describe( 'Shopper → Translations', () => { } ); test.describe( 'Shopper → Tax', () => { - test.beforeAll( async () => { + test.beforeEach( async () => { await cli( `npm run wp-env run tests-cli -- wp option set woocommerce_prices_include_tax no` ); @@ -79,20 +66,10 @@ test.describe( 'Shopper → Tax', () => { ); } ); - test.afterAll( async () => { - await cli( - `npm run wp-env run tests-cli -- wp option set woocommerce_prices_include_tax yes` - ); - await cli( - `npm run wp-env run tests-cli -- wp option set woocommerce_tax_display_cart excl` - ); - } ); - test( 'User can see tax label and price including tax', async ( { frontendUtils, page, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await frontendUtils.goToMiniCart(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart.block_theme.spec.ts index 06c1ac5e0ef6f..d52d4c60eb02e 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/mini-cart/mini-cart.block_theme.spec.ts @@ -112,7 +112,6 @@ test.describe( `${ blockData.name } Block`, () => { frontendUtils, miniCartUtils, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await miniCartUtils.openMiniCart(); @@ -135,7 +134,6 @@ test.describe( `${ blockData.name } Block`, () => { frontendUtils, miniCartUtils, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await miniCartUtils.openMiniCart(); @@ -150,7 +148,6 @@ test.describe( `${ blockData.name } Block`, () => { frontendUtils, miniCartUtils, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await miniCartUtils.openMiniCart(); @@ -171,7 +168,6 @@ test.describe( `${ blockData.name } Block`, () => { frontendUtils, miniCartUtils, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await miniCartUtils.openMiniCart(); @@ -206,7 +202,6 @@ test.describe( `${ blockData.name } Block`, () => { frontendUtils, miniCartUtils, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await miniCartUtils.openMiniCart(); @@ -229,7 +224,6 @@ test.describe( `${ blockData.name } Block`, () => { frontendUtils, miniCartUtils, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await miniCartUtils.openMiniCart(); @@ -242,7 +236,6 @@ test.describe( `${ blockData.name } Block`, () => { frontendUtils, miniCartUtils, } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME ); await miniCartUtils.openMiniCart(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/on-sale-badge/on-sale-badge-single-product-template.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/on-sale-badge/on-sale-badge-single-product-template.block_theme.side_effects.spec.ts index 0ff6eef98b9e3..423143ce6de84 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/on-sale-badge/on-sale-badge-single-product-template.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/on-sale-badge/on-sale-badge-single-product-template.block_theme.side_effects.spec.ts @@ -72,22 +72,13 @@ const getBoundingClientRect = async ( { }; test.describe( `${ blockData.name }`, () => { test.describe( `On the Single Product Template`, () => { - test.beforeEach( - async ( { requestUtils, admin, editorUtils, editor } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); - await admin.visitSiteEditor( { - postId: `woocommerce/woocommerce//${ blockData.slug }`, - postType: 'wp_template', - } ); - await editorUtils.enterEditMode(); - await editor.setContent( '' ); - } - ); - - test.afterEach( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); + test.beforeEach( async ( { admin, editorUtils, editor } ) => { + await admin.visitSiteEditor( { + postId: `woocommerce/woocommerce//${ blockData.slug }`, + postType: 'wp_template', + } ); + await editorUtils.enterEditMode(); + await editor.setContent( '' ); } ); test( 'should be rendered on the editor side', async ( { diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/page-content-wrapper/page-content-wrapper.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/page-content-wrapper/page-content-wrapper.block_theme.side_effects.spec.ts index 3ff9263791f92..45c1dd439ad1f 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/page-content-wrapper/page-content-wrapper.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/page-content-wrapper/page-content-wrapper.block_theme.side_effects.spec.ts @@ -9,6 +9,7 @@ import type { FrontendUtils } from '@woocommerce/e2e-utils'; const templates = [ { title: 'Cart', + slug: 'cart', blockClassName: '.wc-block-cart', visitPage: async ( { frontendUtils, @@ -20,6 +21,7 @@ const templates = [ }, { title: 'Checkout', + slug: 'checkout', blockClassName: '.wc-block-checkout', visitPage: async ( { frontendUtils, @@ -35,21 +37,26 @@ const templates = [ const userText = 'Hello World in the page'; templates.forEach( async ( template ) => { - test.describe( 'Page Content Wrapper', async () => { - test.beforeAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - } ); + test.describe( 'Page Content Wrapper', () => { test( `the content of the ${ template.title } page is correctly rendered in the ${ template.title } template`, async ( { - admin, page, + admin, editorUtils, frontendUtils, + requestUtils, } ) => { - await admin.visitAdminPage( 'edit.php?post_type=page' ); - await page.getByLabel( `“${ template.title }” (Edit)` ).click(); + const pageData = await requestUtils.rest( { + path: 'wp/v2/pages?slug=' + template.slug, + } ); + const pageId = pageData[ 0 ].id; + + await admin.editPost( pageId ); - // Prevent trying to insert the paragraph block before the editor is ready. - await page.locator( template.blockClassName ).waitFor(); + // Prevent trying to insert the paragraph block before the editor is + // ready. + await expect( + page.locator( template.blockClassName ) + ).toBeVisible(); await editorUtils.editor.insertBlock( { name: 'core/paragraph', @@ -61,18 +68,6 @@ templates.forEach( async ( template ) => { // Verify edits are in the template when viewed from the frontend. await template.visitPage( { frontendUtils } ); await expect( page.getByText( userText ).first() ).toBeVisible(); - - // Clean up the paragraph block added before. - await admin.visitAdminPage( 'edit.php?post_type=page' ); - await page.getByLabel( `“${ template.title }” (Edit)` ).click(); - - // Prevent trying to insert the paragraph block before the editor is ready. - await page.locator( template.blockClassName ).waitFor(); - - await editorUtils.removeBlocks( { - name: 'core/paragraph', - } ); - await editorUtils.updatePost(); } ); } ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/price-filter/price-filter.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/price-filter/price-filter.block_theme.side_effects.spec.ts deleted file mode 100644 index ab93e8e3a6944..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/tests/price-filter/price-filter.block_theme.side_effects.spec.ts +++ /dev/null @@ -1,210 +0,0 @@ -/** - * External dependencies - */ -import { BlockData } from '@woocommerce/e2e-types'; -import { test, expect } from '@woocommerce/e2e-playwright-utils'; -import { BASE_URL, cli } from '@woocommerce/e2e-utils'; - -const blockData: BlockData< { - urlSearchParamWhenFilterIsApplied: string; - endpointAPI: string; - placeholderUrl: string; -} > = { - name: 'woocommerce/price-filter', - mainClass: '.wc-block-price-filter', - selectors: { - frontend: {}, - editor: {}, - }, - urlSearchParamWhenFilterIsApplied: '?max_price=5', - endpointAPI: 'max_price=500', - placeholderUrl: `${ BASE_URL }/wp-content/plugins/woocommerce/assets/images/placeholder.png`, -}; - -test.describe( `${ blockData.name } Block - with All products Block`, () => { - test.beforeEach( async ( { admin, page, editor } ) => { - await admin.createNewPost( { legacyCanvas: true } ); - await editor.insertBlock( { name: 'woocommerce/all-products' } ); - await editor.insertBlock( { - name: 'woocommerce/filter-wrapper', - attributes: { - filterType: 'price-filter', - heading: 'Filter By Price', - }, - } ); - await editor.publishPost(); - const url = new URL( page.url() ); - const postId = url.searchParams.get( 'post' ); - - await page.goto( `/?p=${ postId }` ); - - await page - .waitForResponse( - async ( response ) => { - if ( - response.url().includes( 'products/collection-data' ) - ) { - const payload = await response.json(); - // Price range seems to be the last thing to be loaded. - const containsPriceRange = !! payload.price_range; - - return containsPriceRange; - } - return false; - }, - { timeout: 3000 } - ) - .catch( () => { - // Do nothing. This is only to ensure the products are loaded. - // There are multiple requests until the products are fully - // loaded. We need to ensure the page is ready to be interacted - // with, hence the extra check. Ideally, this should be signaled - // by the UI (e.g., by a loading spinner), but we don't have - // that yet. - } ); - } ); - - test( 'should show all products', async ( { frontendUtils } ) => { - const allProductsBlock = await frontendUtils.getBlockByName( - 'woocommerce/all-products' - ); - - const img = allProductsBlock.locator( 'img' ).first(); - - await expect( img ).not.toHaveAttribute( - 'src', - blockData.placeholderUrl - ); - - const products = allProductsBlock.getByRole( 'listitem' ); - - await expect( products ).toHaveCount( 9 ); - } ); - - test( 'should show only products that match the filter', async ( { - page, - frontendUtils, - } ) => { - // The price filter input is initially enabled, but it becomes disabled - // for the time it takes to fetch the data. To avoid setting the filter - // value before the input is properly initialized, we wait for the input - // to be disabled first. This is a safeguard to avoid flakiness which - // should be addressed in the code, but All Products block will be - // deprecated in the future, so we are not going to optimize it. - await page - .getByRole( 'textbox', { - name: 'Filter products by maximum price', - disabled: true, - } ) - .waitFor( { timeout: 3000 } ) - .catch( () => { - // Do not throw in case Playwright doesn't make it in time for the - // initial (pre-request) render. - } ); - - const maxPriceInput = page.getByRole( 'textbox', { - name: 'Filter products by maximum price', - } ); - // await page.pause(); - - await maxPriceInput.dblclick(); - await maxPriceInput.fill( '$5' ); - await maxPriceInput.press( 'Tab' ); - - const allProductsBlock = await frontendUtils.getBlockByName( - 'woocommerce/all-products' - ); - - const img = allProductsBlock.locator( 'img' ).first(); - await expect( img ).not.toHaveAttribute( - 'src', - blockData.placeholderUrl - ); - - const allProducts = allProductsBlock.getByRole( 'listitem' ); - - await expect( allProducts ).toHaveCount( 1 ); - expect( page.url() ).toContain( - blockData.urlSearchParamWhenFilterIsApplied - ); - } ); -} ); -// These tests are disabled because there is an issue with the default contents of this page, possible caused by other tests. -test.describe( `${ blockData.name } Block - with PHP classic template`, () => { - test.beforeAll( async () => { - await cli( - 'npm run wp-env run tests-cli -- wp option update wc_blocks_use_blockified_product_grid_block_as_template false' - ); - } ); - test.beforeEach( async ( { admin, page, editor } ) => { - await admin.visitSiteEditor( { - postId: 'woocommerce/woocommerce//archive-product', - postType: 'wp_template', - } ); - - await editor.canvas.locator( 'body' ).click(); - - await editor.insertBlock( { - name: 'woocommerce/filter-wrapper', - attributes: { - filterType: 'price-filter', - heading: 'Filter By Price', - }, - } ); - await editor.saveSiteEditorEntities(); - await page.goto( `/shop` ); - } ); - - test.afterEach( async ( { templateApiUtils } ) => { - await templateApiUtils.revertTemplate( - 'woocommerce/woocommerce//archive-product' - ); - } ); - - test( 'should show all products', async ( { frontendUtils } ) => { - const legacyTemplate = await frontendUtils.getBlockByName( - 'woocommerce/legacy-template' - ); - - const products = legacyTemplate - .getByRole( 'list' ) - .locator( '.product' ); - - await expect( products ).toHaveCount( 16 ); - } ); - - // eslint-disable-next-line playwright/no-skipped-test - test.skip( 'should show only products that match the filter', async ( { - page, - frontendUtils, - } ) => { - const maxPriceInput = page.getByRole( 'textbox', { - name: 'Filter products by maximum price', - } ); - - await frontendUtils.selectTextInput( maxPriceInput ); - await maxPriceInput.fill( '$5' ); - await maxPriceInput.press( 'Tab' ); - await page.waitForURL( ( url ) => - url - .toString() - .includes( blockData.urlSearchParamWhenFilterIsApplied ) - ); - - const legacyTemplate = await frontendUtils.getBlockByName( - 'woocommerce/legacy-template' - ); - - const products = legacyTemplate - .getByRole( 'list' ) - .locator( '.product' ); - - await expect( products ).toHaveCount( 1 ); - } ); - - test.afterAll( async () => { - await cli( - 'npm run wp-env run tests-cli -- wp option delete wc_blocks_use_blockified_product_grid_block_as_template' - ); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/price-filter/price-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/price-filter/price-filter.block_theme.spec.ts new file mode 100644 index 0000000000000..b67d18bf063c5 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/price-filter/price-filter.block_theme.spec.ts @@ -0,0 +1,405 @@ +/** + * External dependencies + */ +import { test as base, expect } from '@woocommerce/e2e-playwright-utils'; +import { BASE_URL, cli } from '@woocommerce/e2e-utils'; + +/** + * Internal dependencies + */ +import ProductCollectionPage from '../product-collection/product-collection.page'; + +export const blockData = { + slug: 'woocommerce/price-filter', + name: 'Filter by Price', + mainClass: '.wc-block-price-filter', + selectors: { + frontend: {}, + editor: {}, + }, + urlSearchParamWhenFilterIsApplied: 'max_price=5', + endpointAPI: 'max_price=500', + placeholderUrl: `${ BASE_URL }/wp-content/plugins/woocommerce/assets/images/placeholder.png`, +}; + +const test = base.extend< { + productCollectionPageObject: ProductCollectionPage; +} >( { + productCollectionPageObject: async ( + { page, admin, editor, templateApiUtils, editorUtils }, + use + ) => { + const pageObject = new ProductCollectionPage( { + page, + admin, + editor, + templateApiUtils, + editorUtils, + } ); + await use( pageObject ); + }, +} ); + +test.describe( `${ blockData.name } Block - editor side`, () => { + test.beforeEach( async ( { admin, editor } ) => { + await admin.createNewPost(); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'price-filter', + heading: 'Filter By Price', + }, + } ); + await editor.openDocumentSettingsSidebar(); + } ); + + test( "should allow changing the block's title", async ( { page } ) => { + const textSelector = + '.wp-block-woocommerce-filter-wrapper .wp-block-heading'; + + const title = 'New Title'; + + await page.locator( textSelector ).fill( title ); + + await expect( page.locator( textSelector ) ).toHaveText( title ); + } ); + + test( 'should allow changing the display style', async ( { + page, + editorUtils, + editor, + } ) => { + const priceFilterControls = await editorUtils.getBlockByName( + 'woocommerce/price-filter' + ); + await editor.selectBlocks( priceFilterControls ); + + await expect( + priceFilterControls.getByRole( 'textbox', { + name: 'Filter products by minimum', + } ) + ).toBeVisible(); + + await expect( + priceFilterControls.getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + ).toBeVisible(); + + await page + .getByLabel( 'Price Range Selector' ) + .getByText( 'Text' ) + .click(); + + await expect( + priceFilterControls.getByRole( 'textbox', { + name: 'Filter products by minimum', + } ) + ).toBeHidden(); + + await expect( + priceFilterControls.getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + ).toBeHidden(); + } ); + + test( 'should allow toggling the visibility of the filter button', async ( { + page, + editorUtils, + editor, + } ) => { + const priceFilterControls = await editorUtils.getBlockByName( + blockData.slug + ); + await editor.selectBlocks( priceFilterControls ); + + await expect( + priceFilterControls.getByRole( 'button', { + name: 'Apply', + } ) + ).toBeHidden(); + + await page.getByText( "Show 'Apply filters' button" ).click(); + + await expect( + priceFilterControls.getByRole( 'button', { + name: 'Apply', + } ) + ).toBeVisible(); + } ); +} ); + +test.describe( `${ blockData.name } Block - with All products Block`, () => { + test.beforeEach( async ( { admin, page, editor } ) => { + await admin.createNewPost( { legacyCanvas: true } ); + await editor.insertBlock( { name: 'woocommerce/all-products' } ); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'price-filter', + heading: 'Filter By Price', + }, + } ); + + const postId = await editor.publishPost(); + await page.goto( `/?p=${ postId }` ); + + await page + .waitForResponse( + async ( response ) => { + if ( + response.url().includes( 'products/collection-data' ) + ) { + const payload = await response.json(); + // Price range seems to be the last thing to be loaded. + const containsPriceRange = !! payload.price_range; + + return containsPriceRange; + } + return false; + }, + { timeout: 3000 } + ) + .catch( () => { + // Do nothing. This is only to ensure the products are loaded. + // There are multiple requests until the products are fully + // loaded. We need to ensure the page is ready to be interacted + // with, hence the extra check. Ideally, this should be signaled + // by the UI (e.g., by a loading spinner), but we don't have + // that yet. + } ); + } ); + + test( 'should show all products', async ( { frontendUtils } ) => { + const allProductsBlock = await frontendUtils.getBlockByName( + 'woocommerce/all-products' + ); + + const img = allProductsBlock.locator( 'img' ).first(); + + await expect( img ).not.toHaveAttribute( + 'src', + blockData.placeholderUrl + ); + + const products = allProductsBlock.getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 9 ); + } ); + + test( 'should show only products that match the filter', async ( { + page, + frontendUtils, + } ) => { + // The price filter input is initially enabled, but it becomes disabled + // for the time it takes to fetch the data. To avoid setting the filter + // value before the input is properly initialized, we wait for the input + // to be disabled first. This is a safeguard to avoid flakiness which + // should be addressed in the code, but All Products block will be + // deprecated in the future, so we are not going to optimize it. + await page + .getByRole( 'textbox', { + name: 'Filter products by maximum price', + disabled: true, + } ) + .waitFor( { timeout: 3000 } ) + .catch( () => { + // Do not throw in case Playwright doesn't make it in time for the + // initial (pre-request) render. + } ); + + const maxPriceInput = page.getByRole( 'textbox', { + name: 'Filter products by maximum price', + } ); + + await maxPriceInput.dblclick(); + await maxPriceInput.fill( '$5' ); + await maxPriceInput.press( 'Tab' ); + + const allProductsBlock = await frontendUtils.getBlockByName( + 'woocommerce/all-products' + ); + + const img = allProductsBlock.locator( 'img' ).first(); + await expect( img ).not.toHaveAttribute( + 'src', + blockData.placeholderUrl + ); + + const allProducts = allProductsBlock.getByRole( 'listitem' ); + + await expect( allProducts ).toHaveCount( 1 ); + expect( page.url() ).toContain( + blockData.urlSearchParamWhenFilterIsApplied + ); + } ); +} ); +test.describe( `${ blockData.name } Block - with PHP classic template`, () => { + test.beforeEach( async ( { admin, page, editor, editorUtils } ) => { + await cli( + 'npm run wp-env run tests-cli -- wp option update wc_blocks_use_blockified_product_grid_block_as_template false' + ); + + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//archive-product', + postType: 'wp_template', + } ); + + await editorUtils.enterEditMode(); + + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'price-filter', + heading: 'Filter By Price', + }, + } ); + await editor.saveSiteEditorEntities(); + await page.goto( `/shop` ); + } ); + + test( 'should show all products', async ( { frontendUtils } ) => { + const legacyTemplate = await frontendUtils.getBlockByName( + 'woocommerce/legacy-template' + ); + + const products = legacyTemplate + .getByRole( 'list' ) + .locator( '.product' ); + + await expect( products ).toHaveCount( 16 ); + } ); + + test( 'should show only products that match the filter', async ( { + page, + frontendUtils, + } ) => { + const maxPriceInput = page.getByRole( 'textbox', { + name: 'Filter products by maximum price', + } ); + + await frontendUtils.selectTextInput( maxPriceInput ); + await maxPriceInput.fill( '$5' ); + await maxPriceInput.press( 'Tab' ); + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + const legacyTemplate = await frontendUtils.getBlockByName( + 'woocommerce/legacy-template' + ); + + const products = legacyTemplate + .getByRole( 'list' ) + .locator( '.product' ); + + await expect( products ).toHaveCount( 1 ); + } ); +} ); + +test.describe( `${ blockData.name } Block - with Product Collection`, () => { + test.beforeEach( + async ( { + admin, + editorUtils, + productCollectionPageObject, + editor, + } ) => { + await admin.createNewPost(); + await productCollectionPageObject.insertProductCollection(); + await productCollectionPageObject.chooseCollectionInPost( + 'productCatalog' + ); + + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'price-filter', + heading: 'Filter By Price', + }, + } ); + await editorUtils.publishAndVisitPost(); + } + ); + + test( 'should show all products', async ( { page } ) => { + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 9 ); + } ); + + test( 'should show only products that match the filter', async ( { + page, + frontendUtils, + } ) => { + const maxPriceInput = page.getByRole( 'textbox', { + name: 'Filter products by maximum price', + } ); + + await frontendUtils.selectTextInput( maxPriceInput ); + await maxPriceInput.fill( '$5' ); + await maxPriceInput.press( 'Tab' ); + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 1 ); + } ); + + test( 'should refresh the page only if the user click on button', async ( { + page, + admin, + editor, + editorUtils, + productCollectionPageObject, + } ) => { + await admin.createNewPost(); + await productCollectionPageObject.insertProductCollection(); + await productCollectionPageObject.chooseCollectionInPost( + 'productCatalog' + ); + + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'price-filter', + heading: 'Filter By Price', + }, + } ); + + const priceFilterControls = await editorUtils.getBlockByName( + blockData.slug + ); + await editor.selectBlocks( priceFilterControls ); + await editor.openDocumentSettingsSidebar(); + await page.getByText( "Show 'Apply filters' button" ).click(); + await editorUtils.publishAndVisitPost(); + + const maxPriceInput = page.getByRole( 'textbox', { + name: 'Filter products by maximum price', + } ); + + await maxPriceInput.dblclick(); + await maxPriceInput.fill( '$5' ); + await page + .getByRole( 'button', { name: 'Apply price filter' } ) + .click(); + + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 1 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-best-sellers/product-best-sellers.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-best-sellers/product-best-sellers.block_theme.spec.ts new file mode 100644 index 0000000000000..51ffeb2565027 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-best-sellers/product-best-sellers.block_theme.spec.ts @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Best Selling Products', + slug: 'woocommerce/product-best-sellers', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await expect( blockLocator.getByRole( 'listitem' ) ).toHaveCount( 9 ); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( + blockLocatorFrontend.getByRole( 'listitem' ) + ).toHaveCount( 9 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-button/product-button.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-button/product-button.block_theme.side_effects.spec.ts index 0bc5c3eda8d0d..b94435d10a0f0 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-button/product-button.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-button/product-button.block_theme.side_effects.spec.ts @@ -2,10 +2,6 @@ * External dependencies */ import { expect, test } from '@woocommerce/e2e-playwright-utils'; -import { - installPluginFromPHPFile, - uninstallPluginFromPHPFile, -} from '@woocommerce/e2e-mocks/custom-plugins'; /** * Internal dependencies @@ -13,11 +9,7 @@ import { import { blockData, handleAddToCartAjaxSetting } from './utils'; test.describe( `${ blockData.name } Block`, () => { - test.beforeAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - } ); - test.beforeEach( async ( { frontendUtils, storeApiUtils } ) => { - await storeApiUtils.cleanCart(); + test.beforeEach( async ( { frontendUtils } ) => { await frontendUtils.goToShop(); } ); @@ -120,10 +112,11 @@ test.describe( `${ blockData.name } Block`, () => { } ); test( 'the filter `woocommerce_product_add_to_cart_text` should be applied', async ( { + requestUtils, frontendUtils, } ) => { - await installPluginFromPHPFile( - `${ __dirname }/update-product-button-text.php` + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-custom-add-to-cart-button-text' ); await frontendUtils.goToShop(); const blocks = await frontendUtils.getBlockByName( blockData.slug ); @@ -132,11 +125,4 @@ test.describe( `${ blockData.name } Block`, () => { blockData.selectors.frontend.productsToDisplay ); } ); - - test.afterAll( async ( { storeApiUtils } ) => { - await storeApiUtils.cleanCart(); - await uninstallPluginFromPHPFile( - `${ __dirname }/update-product-button-text.php` - ); - } ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-categories/product-categories.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-categories/product-categories.block_theme.spec.ts new file mode 100644 index 0000000000000..f3d37e9a2c659 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-categories/product-categories.block_theme.spec.ts @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Product Categories List', + slug: 'woocommerce/product-categories', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await expect( blockLocator.getByRole( 'listitem' ) ).toHaveCount( 6 ); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( + blockLocatorFrontend.getByRole( 'listitem' ) + ).toHaveCount( 6 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-category/product-category.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-category/product-category.block_theme.spec.ts new file mode 100644 index 0000000000000..4c6d82ae2ac1d --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-category/product-category.block_theme.spec.ts @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Products by Category', + slug: 'woocommerce/product-category', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await blockLocator.getByText( 'Accessories' ).click(); + await blockLocator.getByText( 'Done' ).click(); + await expect( blockLocator.getByRole( 'listitem' ) ).toHaveCount( 5 ); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( + blockLocatorFrontend.getByRole( 'listitem' ) + ).toHaveCount( 5 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/compatibility-layer.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/compatibility-layer.block_theme.side_effects.spec.ts index 3ae2b4daac2eb..4fdf4ea5afca1 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/compatibility-layer.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/compatibility-layer.block_theme.side_effects.spec.ts @@ -2,10 +2,6 @@ * External dependencies */ import { test as base, expect } from '@woocommerce/e2e-playwright-utils'; -import { - installPluginFromPHPFile, - uninstallPluginFromPHPFile, -} from '@woocommerce/e2e-mocks/custom-plugins'; /** * Internal dependencies @@ -79,7 +75,6 @@ const multipleOccurrenceScenarios: Scenario[] = [ }, ]; -const compatibilityPluginFileName = 'compatibility-plugin.php'; const test = base.extend< { pageObject: ProductCollectionPage } >( { pageObject: async ( { page, admin, editor, templateApiUtils, editorUtils }, @@ -97,14 +92,12 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( { } ); test.describe( 'Compatibility Layer with Product Collection block', () => { - test.beforeAll( async () => { - await installPluginFromPHPFile( - `${ __dirname }/${ compatibilityPluginFileName }` - ); - } ); + test.describe( 'Product Archive with Product Collection block', () => { + test.beforeEach( async ( { pageObject, requestUtils } ) => { + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-product-collection-compatibility-layer' + ); - test.describe( 'Product Archive with Product Collection block', async () => { - test.beforeEach( async ( { pageObject } ) => { await pageObject.replaceProductsWithProductCollectionInTemplate( 'woocommerce/woocommerce//archive-product' ); @@ -133,11 +126,4 @@ test.describe( 'Compatibility Layer with Product Collection block', () => { } ); } } ); - - test.afterAll( async ( { requestUtils } ) => { - await uninstallPluginFromPHPFile( - `${ __dirname }/${ compatibilityPluginFileName }` - ); - await requestUtils.deleteAllTemplates( 'wp_template' ); - } ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.side_effects.spec.ts index 4db1c88f7cba6..698114b7eb60c 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.side_effects.spec.ts @@ -47,7 +47,7 @@ test.describe( 'Product Collection', () => { await expect( pageObject.addToCartButtons ).toHaveCount( 9 ); } ); - test.describe( 'Renders correctly with all Product Elements', async () => { + test.describe( 'Renders correctly with all Product Elements', () => { const insertProductElements = async ( pageObject: ProductCollectionPage ) => { @@ -177,21 +177,23 @@ test.describe( 'Product Collection', () => { test( 'Order By - sort products by title in descending order correctly', async ( { pageObject, } ) => { - await pageObject.setOrderBy( 'title/desc' ); - const allTitles = await pageObject.productTitles.allInnerTexts(); - const expectedTitles = [ ...allTitles ].sort().reverse(); + const sortedTitles = [ + 'WordPress Pennant', + 'V-Neck T-Shirt', + 'T-Shirt with Logo', + 'T-Shirt', + /Sunglasses/, // In the frontend it's "Protected: Sunglasses" + 'Single', + 'Polo', + 'Long Sleeve Tee', + 'Logo Collection', + ]; - expect( allTitles ).toStrictEqual( expectedTitles ); + await pageObject.setOrderBy( 'title/desc' ); + await expect( pageObject.productTitles ).toHaveText( sortedTitles ); await pageObject.publishAndGoToFrontend(); - - const frontendTitles = - await pageObject.productTitles.allInnerTexts(); - expect( - frontendTitles.map( ( title ) => - title.replace( 'Protected: ', '' ) - ) - ).toStrictEqual( expectedTitles ); + await expect( pageObject.productTitles ).toHaveText( sortedTitles ); } ); // Products can be filtered based on 'on sale' status. @@ -258,7 +260,7 @@ test.describe( 'Product Collection', () => { pageObject, } ) => { const filterName = 'Product categories'; - await pageObject.addFilter( 'Show Taxonomies' ); + await pageObject.addFilter( 'Show product categories' ); await pageObject.setFilterComboboxValue( filterName, [ 'Clothing', ] ); @@ -298,7 +300,7 @@ test.describe( 'Product Collection', () => { pageObject, } ) => { const filterName = 'Product tags'; - await pageObject.addFilter( 'Show Taxonomies' ); + await pageObject.addFilter( 'Show product tags' ); await pageObject.setFilterComboboxValue( filterName, [ 'Recommended', ] ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts index 2bb88aea736c4..8f45fb77b6555 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts @@ -250,7 +250,8 @@ class ProductCollectionPage { name: | 'Show Hand-picked Products' | 'Keyword' - | 'Show Taxonomies' + | 'Show product categories' + | 'Show product tags' | 'Show Product Attributes' | 'Featured' | 'Created' diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image-next-previous/product-gallery-large-image-next-previous.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image-next-previous/product-gallery-large-image-next-previous.block_theme.side_effects.spec.ts index 877c05a0d903a..d84cf6002ca26 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image-next-previous/product-gallery-large-image-next-previous.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image-next-previous/product-gallery-large-image-next-previous.block_theme.side_effects.spec.ts @@ -87,9 +87,7 @@ const test = base.extend< { pageObject: ProductGalleryPage } >( { } ); test.describe( `${ blockData.name }`, () => { - test.beforeEach( async ( { requestUtils, admin, editorUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); + test.beforeEach( async ( { admin, editorUtils } ) => { await admin.visitSiteEditor( { postId: `woocommerce/woocommerce//${ blockData.slug }`, postType: 'wp_template', @@ -97,11 +95,6 @@ test.describe( `${ blockData.name }`, () => { await editorUtils.enterEditMode(); } ); - test.afterEach( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); - } ); - // eslint-disable-next-line playwright/no-skipped-test test.skip( 'Renders Next/Previous Button block on the editor side', async ( { editor, @@ -160,28 +153,26 @@ test.describe( `${ blockData.name }`, () => { .locator( blockData.selectors.editor.noArrowsOption ) .click(); - const isVisible = await page - .locator( - '.wc-block-product-gallery-large-image-next-previous-container' - ) - .isVisible(); + const container = page.locator( + '.wc-block-product-gallery-large-image-next-previous-container' + ); - expect( isVisible ).toBe( false ); + await expect( container ).toBeHidden(); await editor.saveSiteEditorEntities(); await page.goto( blockData.productPage ); - const leftArrow = await page - .locator( blockData.selectors.editor.leftArrow.off ) - .isVisible(); + const leftArrow = page.locator( + blockData.selectors.editor.leftArrow.off + ); - const rightArrow = await page - .locator( blockData.selectors.editor.rightArrow.off ) - .isVisible(); + const rightArrow = page.locator( + blockData.selectors.editor.rightArrow.off + ); - expect( leftArrow ).toBe( false ); - expect( rightArrow ).toBe( false ); + await expect( leftArrow ).toBeHidden(); + await expect( rightArrow ).toBeHidden(); } ); // eslint-disable-next-line playwright/no-skipped-test diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image/product-gallery-large-image.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image/product-gallery-large-image.block_theme.side_effects.spec.ts index 660fc9c4325bc..78917b2a8946d 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image/product-gallery-large-image.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image/product-gallery-large-image.block_theme.side_effects.spec.ts @@ -31,9 +31,7 @@ const test = base.extend< { pageObject: ProductGalleryPage } >( { } ); test.describe( `${ blockData.name }`, () => { - test.beforeEach( async ( { requestUtils, admin, editorUtils, editor } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); + test.beforeEach( async ( { admin, editorUtils, editor } ) => { await admin.visitSiteEditor( { postId: `woocommerce/woocommerce//${ blockData.slug }`, postType: 'wp_template', @@ -42,11 +40,6 @@ test.describe( `${ blockData.name }`, () => { await editor.openDocumentSettingsSidebar(); } ); - test.afterEach( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); - } ); - test( 'Renders Product Gallery Large Image block on the editor and frontend side', async ( { page, editor, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-pager/product-gallery-pager.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-pager/product-gallery-pager.block_theme.side_effects.spec.ts index a9a87e7bacb42..746cca37d2259 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-pager/product-gallery-pager.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-pager/product-gallery-pager.block_theme.side_effects.spec.ts @@ -44,11 +44,6 @@ const test = base.extend< { pageObject: ProductGalleryPage } >( { } ); test.describe( `${ blockData.name }`, () => { - test.beforeAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); - } ); - test.beforeEach( async ( { admin, editorUtils, editor } ) => { await admin.visitSiteEditor( { postId: `woocommerce/woocommerce//${ blockData.slug }`, @@ -58,11 +53,6 @@ test.describe( `${ blockData.name }`, () => { await editor.openDocumentSettingsSidebar(); } ); - test.afterEach( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); - } ); - test( 'Renders Product Gallery Pager block on the editor and frontend side', async ( { page, editor, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts index 46c06677373c5..0e44415dc307b 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts @@ -48,9 +48,7 @@ const test = base.extend< { pageObject: ProductGalleryPage } >( { }, } ); test.describe( `${ blockData.name }`, () => { - test.beforeEach( async ( { requestUtils, admin, editorUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); + test.beforeEach( async ( { admin, editorUtils } ) => { await admin.visitSiteEditor( { postId: `woocommerce/woocommerce//${ blockData.slug }`, postType: 'wp_template', @@ -143,11 +141,11 @@ test.describe( `${ blockData.name }`, () => { .locator( blockData.selectors.editor.noThumbnailsOption ) .click(); - const isVisible = await page - .locator( blockData.selectors.editor.thumbnails ) - .isVisible(); + const element = page.locator( + blockData.selectors.editor.thumbnails + ); - expect( isVisible ).toBe( false ); + await expect( element ).toBeHidden(); await editor.saveSiteEditorEntities(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts index 63aeaed6d45da..2ff960b88d22f 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts @@ -83,9 +83,7 @@ const getThumbnailImageIdByNth = async ( }; test.describe( `${ blockData.name }`, () => { - test.beforeEach( async ( { requestUtils, admin, editorUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); + test.beforeEach( async ( { admin, editorUtils } ) => { await admin.visitSiteEditor( { postId: `woocommerce/woocommerce//${ blockData.slug }`, postType: 'wp_template', @@ -93,11 +91,6 @@ test.describe( `${ blockData.name }`, () => { await editorUtils.enterEditMode(); } ); - test.afterEach( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); - } ); - test.describe( 'with thumbnails', () => { test( 'should have as first thumbnail, the same image that it is visible in the Large Image block', async ( { page, @@ -570,6 +563,7 @@ test.describe( `${ blockData.name }`, () => { editor, pageObject, } ) => { + await editor.openDocumentSettingsSidebar(); await pageObject.addProductGalleryBlock( { cleanContent: true } ); await page diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-new/product-new.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-new/product-new.block_theme.spec.ts new file mode 100644 index 0000000000000..fd07d08980d12 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-new/product-new.block_theme.spec.ts @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Newest Products', + slug: 'woocommerce/product-new', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await expect( blockLocator.getByRole( 'listitem' ) ).toHaveCount( 9 ); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( + blockLocatorFrontend.getByRole( 'listitem' ) + ).toHaveCount( 9 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-on-sale/product-on-sale.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-on-sale/product-on-sale.block_theme.spec.ts new file mode 100644 index 0000000000000..7d2ea007ab9eb --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-on-sale/product-on-sale.block_theme.spec.ts @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'On Sale Products', + slug: 'woocommerce/product-on-sale', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await expect( blockLocator.getByRole( 'listitem' ) ).toHaveCount( 6 ); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( + blockLocatorFrontend.getByRole( 'listitem' ) + ).toHaveCount( 6 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-results-count/product-results-count.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-results-count/product-results-count.block_theme.spec.ts new file mode 100644 index 0000000000000..ad4cc34296109 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-results-count/product-results-count.block_theme.spec.ts @@ -0,0 +1,70 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Product Results Count', + slug: 'woocommerce/product-results-count', + class: '.wc-block-product-results-count', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( "block can't be inserted in Post Editor", async ( { + editor, + admin, + } ) => { + await admin.createNewPost(); + await expect( + editor.insertBlock( { name: blockData.slug } ) + ).rejects.toThrow( + new RegExp( `Block type '${ blockData.slug }' is not registered.` ) + ); + } ); + + test( 'block should be already added in the Product Catalog Template', async ( { + editorUtils, + admin, + } ) => { + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//archive-product', + postType: 'wp_template', + } ); + await editorUtils.enterEditMode(); + const alreadyPresentBlock = await editorUtils.getBlockByName( + blockData.slug + ); + + await expect( alreadyPresentBlock ).toHaveText( + 'Showing 1-X of X results' + ); + } ); + + test( 'block can be inserted in the Site Editor', async ( { + admin, + requestUtils, + editorUtils, + editor, + } ) => { + const template = await requestUtils.createTemplate( 'wp_template', { + slug: 'sorter', + title: 'Sorter', + content: 'howdy', + } ); + + await admin.visitSiteEditor( { + postId: template.id, + postType: 'wp_template', + } ); + + await editorUtils.enterEditMode(); + + await editor.insertBlock( { + name: blockData.slug, + } ); + + const block = await editorUtils.getBlockByName( blockData.slug ); + + await expect( block ).toHaveText( 'Showing 1-X of X results' ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-tag/product-tag.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-tag/product-tag.block_theme.spec.ts new file mode 100644 index 0000000000000..afb5ef3ef4daa --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-tag/product-tag.block_theme.spec.ts @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Products by Tag', + slug: 'woocommerce/product-tag', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await blockLocator.getByText( 'Recommended' ).click(); + await blockLocator.getByText( 'Done' ).click(); + await expect( blockLocator.getByRole( 'listitem' ) ).toHaveCount( 2 ); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( + blockLocatorFrontend.getByRole( 'listitem' ) + ).toHaveCount( 2 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-top-rated/product-top-rated.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-top-rated/product-top-rated.block_theme.spec.ts new file mode 100644 index 0000000000000..3b2abd7d1304a --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-top-rated/product-top-rated.block_theme.spec.ts @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Top Rated Products', + slug: 'woocommerce/product-top-rated', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await expect( blockLocator.getByRole( 'listitem' ) ).toHaveCount( 9 ); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( + blockLocatorFrontend.getByRole( 'listitem' ) + ).toHaveCount( 9 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/products-by-attribute/products-by-attribute.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/products-by-attribute/products-by-attribute.block_theme.spec.ts new file mode 100644 index 0000000000000..c38c5d736d4a4 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/products-by-attribute/products-by-attribute.block_theme.spec.ts @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +const blockData = { + name: 'Products by Attribute', + slug: 'woocommerce/products-by-attribute', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'can be inserted in Post Editor and it is visible on the frontend', async ( { + editorUtils, + editor, + admin, + frontendUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockData.slug } ); + const blockLocator = await editorUtils.getBlockByName( blockData.slug ); + await blockLocator.getByText( 'Color' ).click(); + await blockLocator.getByText( 'Done' ).click(); + await expect( blockLocator.getByRole( 'listitem' ) ).toHaveCount( 9 ); + await editorUtils.publishAndVisitPost(); + const blockLocatorFrontend = await frontendUtils.getBlockByName( + blockData.slug + ); + await expect( + blockLocatorFrontend.getByRole( 'listitem' ) + ).toHaveCount( 9 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/products/products.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/products/products.block_theme.side_effects.spec.ts index 8c6dc21e5cdab..4c2590b6bca6d 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/products/products.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/products/products.block_theme.side_effects.spec.ts @@ -123,10 +123,6 @@ for ( const { legacyBlockName, } of Object.values( templates ) ) { test.describe( `${ templateTitle } template`, () => { - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); - } ); test( 'Products block matches with classic template block', async ( { admin, editor, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/rating-filter/rating-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/rating-filter/rating-filter.block_theme.spec.ts new file mode 100644 index 0000000000000..4f02266d4ed78 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/rating-filter/rating-filter.block_theme.spec.ts @@ -0,0 +1,270 @@ +/** + * External dependencies + */ +import { test as base, expect } from '@woocommerce/e2e-playwright-utils'; +import { cli } from '@woocommerce/e2e-utils'; + +/** + * Internal dependencies + */ +import ProductCollectionPage from '../product-collection/product-collection.page'; + +const blockData = { + name: 'Filter by Rating', + slug: 'woocommerce/rating-filter', + urlSearchParamWhenFilterIsApplied: 'rating_filter=1', +}; + +const test = base.extend< { + productCollectionPageObject: ProductCollectionPage; +} >( { + productCollectionPageObject: async ( + { page, admin, editor, templateApiUtils, editorUtils }, + use + ) => { + const pageObject = new ProductCollectionPage( { + page, + admin, + editor, + templateApiUtils, + editorUtils, + } ); + await use( pageObject ); + }, +} ); + +test.describe( `${ blockData.name } Block`, () => { + test.beforeEach( async ( { admin, editor } ) => { + await admin.createNewPost(); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'rating-filter', + heading: 'Filter By Rating', + }, + } ); + await editor.openDocumentSettingsSidebar(); + } ); + + test( "should allow changing the block's title", async ( { page } ) => { + const textSelector = + '.wp-block-woocommerce-filter-wrapper .wp-block-heading'; + + const title = 'New Title'; + + await page.locator( textSelector ).fill( title ); + + await expect( page.locator( textSelector ) ).toHaveText( title ); + } ); + + test( 'should allow changing the display style', async ( { + page, + editorUtils, + editor, + } ) => { + const stockFilter = await editorUtils.getBlockByName( blockData.slug ); + await editor.selectBlocks( stockFilter ); + + await expect( + page.getByRole( 'checkbox', { name: 'Rated 1 out of 5' } ) + ).toBeVisible(); + + await page.getByLabel( 'DropDown' ).click(); + + await expect( + stockFilter.getByRole( 'checkbox', { + name: 'In Stock', + } ) + ).toBeHidden(); + + await expect( + page.getByRole( 'checkbox', { name: 'Rated 1 out of 5' } ) + ).toBeHidden(); + + await expect( page.getByRole( 'combobox' ) ).toBeVisible(); + } ); + + test( 'should allow toggling the visibility of the filter button', async ( { + page, + editorUtils, + editor, + } ) => { + const priceFilterControls = await editorUtils.getBlockByName( + blockData.slug + ); + await editor.selectBlocks( priceFilterControls ); + + await expect( + priceFilterControls.getByRole( 'button', { + name: 'Apply', + } ) + ).toBeHidden(); + + await page.getByText( "Show 'Apply filters' button" ).click(); + + await expect( + priceFilterControls.getByRole( 'button', { + name: 'Apply', + } ) + ).toBeVisible(); + } ); +} ); + +test.describe( `${ blockData.name } Block - with PHP classic template`, () => { + test.beforeEach( async ( { admin, page, editor, editorUtils } ) => { + await cli( + 'npm run wp-env run tests-cli -- wp option update wc_blocks_use_blockified_product_grid_block_as_template false' + ); + + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//archive-product', + postType: 'wp_template', + } ); + + await editorUtils.enterEditMode(); + + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'rating-filter', + heading: 'Filter By Rating', + }, + } ); + await editor.saveSiteEditorEntities(); + await page.goto( `/shop` ); + } ); + + test( 'should show all products', async ( { frontendUtils, page } ) => { + const legacyTemplate = await frontendUtils.getBlockByName( + 'woocommerce/legacy-template' + ); + + const products = legacyTemplate + .getByRole( 'list' ) + .locator( '.product' ); + + await expect( products ).toHaveCount( 16 ); + + await expect( + page.getByRole( 'checkbox', { name: 'Rated 1 out of 5' } ) + ).toBeVisible(); + } ); + + test( 'should show only products that match the filter', async ( { + frontendUtils, + page, + } ) => { + await page + .getByRole( 'checkbox', { name: 'Rated 1 out of 5' } ) + .click(); + + const legacyTemplate = await frontendUtils.getBlockByName( + 'woocommerce/legacy-template' + ); + + const products = legacyTemplate + .getByRole( 'list' ) + .locator( '.product' ); + + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + await expect( products ).toHaveCount( 1 ); + } ); +} ); + +test.describe( `${ blockData.name } Block - with Product Collection`, () => { + test.beforeEach( + async ( { + admin, + editorUtils, + productCollectionPageObject, + editor, + } ) => { + await admin.createNewPost(); + await productCollectionPageObject.insertProductCollection(); + await productCollectionPageObject.chooseCollectionInPost( + 'productCatalog' + ); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'rating-filter', + heading: 'Filter By Rating', + }, + } ); + await editorUtils.publishAndVisitPost(); + } + ); + + test( 'should show all products', async ( { page } ) => { + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 9 ); + } ); + + test( 'should show only products that match the filter', async ( { + page, + } ) => { + await page + .getByRole( 'checkbox', { name: 'Rated 1 out of 5' } ) + .click(); + + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 1 ); + } ); + + test( 'should refresh the page only if the user click on button', async ( { + page, + admin, + editor, + editorUtils, + productCollectionPageObject, + } ) => { + await admin.createNewPost(); + await productCollectionPageObject.insertProductCollection(); + await productCollectionPageObject.chooseCollectionInPost( + 'productCatalog' + ); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'rating-filter', + heading: 'Filter By Rating', + }, + } ); + + const ratingFilterControls = await editorUtils.getBlockByName( + 'woocommerce/rating-filter' + ); + await editor.selectBlocks( ratingFilterControls ); + await editor.openDocumentSettingsSidebar(); + await page.getByText( "Show 'Apply filters' button" ).click(); + await editorUtils.publishAndVisitPost(); + + await page + .getByRole( 'checkbox', { name: 'Rated 1 out of 5' } ) + .click(); + await page.getByRole( 'button', { name: 'Apply' } ).click(); + + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 1 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/single-product-details/single-product-details.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/single-product-details/single-product-details.block_theme.spec.ts new file mode 100644 index 0000000000000..43e6263d4120f --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/single-product-details/single-product-details.block_theme.spec.ts @@ -0,0 +1,76 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +/** + * Internal dependencies + */ + +const blockData = { + name: 'Product Details', + slug: 'woocommerce/product-details', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( "block can't be inserted in Post Editor", async ( { + editor, + admin, + } ) => { + await admin.createNewPost(); + await expect( + editor.insertBlock( { name: blockData.slug } ) + ).rejects.toThrow( + new RegExp( `Block type '${ blockData.slug }' is not registered.` ) + ); + } ); + + test( 'block should be already added in the Single Product Template', async ( { + editorUtils, + admin, + } ) => { + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//single-product', + postType: 'wp_template', + } ); + await editorUtils.enterEditMode(); + const alreadyPresentBlock = await editorUtils.getBlockByName( + blockData.slug + ); + + await expect( alreadyPresentBlock ).toHaveText( + /This block lists description, attributes and reviews for a single product./ + ); + } ); + + test( 'block can be inserted in the Site Editor', async ( { + admin, + requestUtils, + editorUtils, + editor, + } ) => { + const template = await requestUtils.createTemplate( 'wp_template', { + // Single Product Details block is addable only in Single Product Templates + slug: 'single-product-v-neck-t-shirt', + title: 'Sorter', + content: 'howdy', + } ); + + await admin.visitSiteEditor( { + postId: template.id, + postType: 'wp_template', + } ); + + await editorUtils.enterEditMode(); + + await editor.insertBlock( { + name: blockData.slug, + } ); + + const block = await editorUtils.getBlockByName( blockData.slug ); + + await expect( block ).toHaveText( + /This block lists description, attributes and reviews for a single product./ + ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/single-product-template/single-product-template-compatibility-layer.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/single-product-template/single-product-template-compatibility-layer.spec.ts index 77d6cc511dadb..e6c1ac75209e8 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/single-product-template/single-product-template-compatibility-layer.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/single-product-template/single-product-template-compatibility-layer.spec.ts @@ -2,10 +2,6 @@ * External dependencies */ import { test, expect } from '@woocommerce/e2e-playwright-utils'; -import { - installPluginFromPHPFile, - uninstallPluginFromPHPFile, -} from '@woocommerce/e2e-mocks/custom-plugins'; /** * Internal dependencies @@ -87,24 +83,20 @@ const singleOccurranceScenarios: Scenario[] = [ }, ]; -const compatiblityPluginFileName = 'compatibility-plugin.php'; - test.describe( 'Compatibility Layer with Product Collection block', () => { - test.beforeAll( async () => { - await installPluginFromPHPFile( - `${ __dirname }/${ compatiblityPluginFileName }` + test.beforeEach( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-single-product-template-compatibility-layer' ); } ); + // eslint-disable-next-line playwright/valid-describe-callback test.describe( 'Product Archive with Product Collection block', async () => { - test.beforeAll( async ( { page } ) => { - await page.goto( '/product/hoodie/' ); - } ); - for ( const scenario of singleOccurranceScenarios ) { test( `${ scenario.title } is attached to the page`, async ( { page, } ) => { + await page.goto( '/product/hoodie/' ); const hooks = page.getByTestId( scenario.dataTestId ); await expect( hooks ).toHaveCount( scenario.amount ); @@ -113,10 +105,3 @@ test.describe( 'Compatibility Layer with Product Collection block', () => { } } ); } ); - -test.afterAll( async ( { requestUtils } ) => { - await uninstallPluginFromPHPFile( - `${ __dirname }/${ compatiblityPluginFileName }` - ); - await requestUtils.deleteAllTemplates( 'wp_template' ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/single-product-template/single-product-template.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/single-product-template/single-product-template.block_theme.spec.ts index 20d6da8491775..bf8fc6aa37769 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/single-product-template/single-product-template.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/single-product-template/single-product-template.block_theme.spec.ts @@ -41,7 +41,7 @@ const products = [ ]; for ( const { classes, product, frontendPage } of products ) { - test.describe( `The Single Product page of the ${ product }`, () => + test.describe( `The Single Product page of the ${ product }`, () => { test( 'add product specific classes to the body', async ( { page, } ) => { @@ -52,7 +52,8 @@ for ( const { classes, product, frontendPage } of products ) { classes.forEach( ( className ) => { expect( bodyClasses?.split( ' ' ) ).toContain( className ); } ); - } ) ); + } ); + } ); } test( 'shows password form in products protected with password', async ( { diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/stock-filter/stock-filter.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/stock-filter/stock-filter.block_theme.spec.ts new file mode 100644 index 0000000000000..d772263f97800 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/stock-filter/stock-filter.block_theme.spec.ts @@ -0,0 +1,276 @@ +/** + * External dependencies + */ +import { test as base, expect } from '@woocommerce/e2e-playwright-utils'; +import { cli } from '@woocommerce/e2e-utils'; + +/** + * Internal dependencies + */ +import ProductCollectionPage from '../product-collection/product-collection.page'; + +export const blockData = { + name: 'Filter by Stock', + slug: 'woocommerce/stock-filter', + urlSearchParamWhenFilterIsApplied: 'filter_stock_status=outofstock', +}; + +const test = base.extend< { + productCollectionPageObject: ProductCollectionPage; +} >( { + productCollectionPageObject: async ( + { page, admin, editor, templateApiUtils, editorUtils }, + use + ) => { + const pageObject = new ProductCollectionPage( { + page, + admin, + editor, + templateApiUtils, + editorUtils, + } ); + await use( pageObject ); + }, +} ); + +test.describe( `${ blockData.name } Block`, () => { + test.beforeEach( async ( { admin, editor } ) => { + await admin.createNewPost(); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'stock-filter', + heading: 'Filter By Price', + }, + } ); + + await editor.openDocumentSettingsSidebar(); + } ); + + test( "should allow changing the block's title", async ( { page } ) => { + const textSelector = + '.wp-block-woocommerce-filter-wrapper .wp-block-heading'; + + const title = 'New Title'; + + await page.locator( textSelector ).fill( title ); + + await expect( page.locator( textSelector ) ).toHaveText( title ); + } ); + + test( 'should allow changing the display style', async ( { + page, + editorUtils, + editor, + } ) => { + const stockFilter = await editorUtils.getBlockByName( blockData.slug ); + await editor.selectBlocks( stockFilter ); + + await expect( + stockFilter.getByRole( 'checkbox', { + name: 'In Stock', + } ) + ).toBeVisible(); + + await expect( + stockFilter.getByRole( 'checkbox', { + name: 'Out of Stock', + } ) + ).toBeVisible(); + + await page.getByLabel( 'DropDown' ).click(); + + await expect( + stockFilter.getByRole( 'checkbox', { + name: 'In Stock', + } ) + ).toBeHidden(); + + await expect( + stockFilter.getByRole( 'checkbox', { + name: 'Out of Stock', + } ) + ).toBeHidden(); + + await expect( page.getByRole( 'combobox' ) ).toBeVisible(); + } ); + + test( 'should allow toggling the visibility of the filter button', async ( { + page, + editorUtils, + editor, + } ) => { + const priceFilterControls = await editorUtils.getBlockByName( + blockData.slug + ); + await editor.selectBlocks( priceFilterControls ); + + await expect( + priceFilterControls.getByRole( 'button', { + name: 'Apply', + } ) + ).toBeHidden(); + + await page.getByText( "Show 'Apply filters' button" ).click(); + + await expect( + priceFilterControls.getByRole( 'button', { + name: 'Apply', + } ) + ).toBeVisible(); + } ); +} ); + +test.describe( `${ blockData.name } Block - with PHP classic template`, () => { + test.beforeEach( async ( { admin, page, editor, editorUtils } ) => { + await cli( + 'npm run wp-env run tests-cli -- wp option update wc_blocks_use_blockified_product_grid_block_as_template false' + ); + + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//archive-product', + postType: 'wp_template', + } ); + + await editorUtils.enterEditMode(); + + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'stock-filter', + heading: 'Filter By Price', + }, + } ); + await editor.saveSiteEditorEntities(); + await page.goto( `/shop` ); + } ); + + test( 'should show all products', async ( { frontendUtils } ) => { + const legacyTemplate = await frontendUtils.getBlockByName( + 'woocommerce/legacy-template' + ); + + const stockFilter = await frontendUtils.getBlockByName( + 'woocommerce/filter-wrapper' + ); + + const products = legacyTemplate + .getByRole( 'list' ) + .locator( '.product' ); + + await expect( products ).toHaveCount( 16 ); + + await expect( stockFilter.getByText( 'In Stock' ) ).toBeVisible(); + await expect( stockFilter.getByText( 'Out of Stock' ) ).toBeVisible(); + } ); + + test( 'should show only products that match the filter', async ( { + frontendUtils, + } ) => { + const stockFilter = await frontendUtils.getBlockByName( + 'woocommerce/filter-wrapper' + ); + + await stockFilter.getByText( 'Out of Stock' ).click(); + + const legacyTemplate = await frontendUtils.getBlockByName( + 'woocommerce/legacy-template' + ); + + const products = legacyTemplate + .getByRole( 'list' ) + .locator( '.product' ); + + await expect( products ).toHaveCount( 1 ); + } ); +} ); + +test.describe( `${ blockData.name } Block - with Product Collection`, () => { + test.beforeEach( + async ( { + admin, + editorUtils, + productCollectionPageObject, + editor, + } ) => { + await admin.createNewPost(); + await productCollectionPageObject.insertProductCollection(); + await productCollectionPageObject.chooseCollectionInPost( + 'productCatalog' + ); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'stock-filter', + heading: 'Filter By Stock', + }, + } ); + await editorUtils.publishAndVisitPost(); + } + ); + + test( 'should show all products', async ( { page } ) => { + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 9 ); + } ); + + test( 'should show only products that match the filter', async ( { + page, + } ) => { + await page.getByText( 'Out of Stock' ).click(); + + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 1 ); + } ); + + test( 'should refresh the page only if the user click on button', async ( { + page, + admin, + editor, + editorUtils, + productCollectionPageObject, + } ) => { + await admin.createNewPost(); + await productCollectionPageObject.insertProductCollection(); + await productCollectionPageObject.chooseCollectionInPost( + 'productCatalog' + ); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { + filterType: 'stock-filter', + heading: 'Filter By Price', + }, + } ); + const stockFilterControls = await editorUtils.getBlockByName( + blockData.slug + ); + await editor.selectBlocks( stockFilterControls ); + await editor.openDocumentSettingsSidebar(); + await page.getByText( "Show 'Apply filters' button" ).click(); + await editorUtils.publishAndVisitPost(); + + await page.getByText( 'Out of Stock' ).click(); + await page.getByRole( 'button', { name: 'Apply' } ).click(); + + await expect( page ).toHaveURL( + new RegExp( blockData.urlSearchParamWhenFilterIsApplied ) + ); + + const products = page + .locator( '.wp-block-woocommerce-product-template' ) + .getByRole( 'listitem' ); + + await expect( products ).toHaveCount( 1 ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/store-notices/store-notices.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/store-notices/store-notices.block_theme.spec.ts new file mode 100644 index 0000000000000..4f43670468ec7 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/store-notices/store-notices.block_theme.spec.ts @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +import { expect, test } from '@woocommerce/e2e-playwright-utils'; + +/** + * Internal dependencies + */ + +const blockData = { + name: 'Store Notices', + slug: 'woocommerce/store-notices', +}; + +test.describe( `${ blockData.slug } Block`, () => { + test( 'should be visible on the Product Catalog template', async ( { + editorUtils, + admin, + } ) => { + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//archive-product', + postType: 'wp_template', + } ); + await editorUtils.enterEditMode(); + const block = await editorUtils.getBlockByName( blockData.slug ); + await expect( block ).toBeVisible(); + await expect( block ).toHaveText( + 'Notices added by WooCommerce or extensions will show up here.' + ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/cart-template.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/cart-template.block_theme.spec.ts index d66b3e0f7d863..2242c6ddefa28 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/templates/cart-template.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/cart-template.block_theme.spec.ts @@ -7,7 +7,7 @@ const permalink = '/cart'; const templatePath = 'woocommerce/woocommerce//page-cart'; const templateType = 'wp_template'; -test.describe( 'Test the cart template', async () => { +test.describe( 'Test the cart template', () => { test( 'Template can be opened in the site editor', async ( { admin, page, @@ -32,7 +32,7 @@ test.describe( 'Test the cart template', async () => { page, editorUtils, } ) => { - await admin.visitAdminPage( 'site-editor.php', 'path=%2Fpage' ); + await admin.visitSiteEditor( { path: '/page' } ); await editor.page .getByRole( 'button', { name: 'Cart', exact: true } ) .click(); @@ -63,12 +63,7 @@ test.describe( 'Test the cart template', async () => { } ); } ); -test.describe( 'Test editing the cart template', async () => { - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); - } ); - +test.describe( 'Test editing the cart template', () => { test( 'Merchant can transform shortcode block into blocks', async ( { admin, editorUtils, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/checkout-template.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/checkout-template.block_theme.spec.ts index c19515994abf0..70d5ece3941c6 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/templates/checkout-template.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/checkout-template.block_theme.spec.ts @@ -7,7 +7,7 @@ const permalink = '/checkout'; const templatePath = 'woocommerce/woocommerce//page-checkout'; const templateType = 'wp_template'; -test.describe( 'Test the checkout template', async () => { +test.describe( 'Test the checkout template', () => { test( 'Template can be opened in the site editor', async ( { admin, page, @@ -36,7 +36,7 @@ test.describe( 'Test the checkout template', async () => { postId: templatePath, postType: templateType, } ); - await admin.visitAdminPage( 'site-editor.php', 'path=%2Fpage' ); + await admin.visitSiteEditor( { path: '/page' } ); await editor.page .getByRole( 'button', { name: 'Checkout', exact: true } ) .click(); @@ -72,12 +72,7 @@ test.describe( 'Test the checkout template', async () => { } ); } ); -test.describe( 'Test editing the checkout template', async () => { - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - await requestUtils.deleteAllTemplates( 'wp_template_part' ); - } ); - +test.describe( 'Test editing the checkout template', () => { test( 'Merchant can transform shortcode block into blocks', async ( { admin, editorUtils, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/constants.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/constants.ts index 7a71b8627997c..236814bc3b184 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/templates/constants.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/constants.ts @@ -87,7 +87,6 @@ export const CUSTOMIZABLE_WC_TEMPLATES: TemplateCustomizationTest[] = [ }, { visitPage: async ( { frontendUtils } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart(); const block = await frontendUtils.getBlockByName( @@ -110,7 +109,6 @@ export const CUSTOMIZABLE_WC_TEMPLATES: TemplateCustomizationTest[] = [ }, { visitPage: async ( { frontendUtils } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart(); await frontendUtils.goToCheckout(); @@ -122,7 +120,6 @@ export const CUSTOMIZABLE_WC_TEMPLATES: TemplateCustomizationTest[] = [ }, { visitPage: async ( { frontendUtils } ) => { - await frontendUtils.emptyCart(); await frontendUtils.goToShop(); await frontendUtils.addToCart(); await frontendUtils.goToCheckout(); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/legacy-templates.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/legacy-templates.block_theme.spec.ts new file mode 100644 index 0000000000000..b02cad4342870 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/legacy-templates.block_theme.spec.ts @@ -0,0 +1,104 @@ +/** + * External dependencies + */ +import { test, expect } from '@woocommerce/e2e-playwright-utils'; +import { cli } from '@woocommerce/e2e-utils'; + +test.describe( 'Legacy templates', () => { + test.beforeEach( async ( { requestUtils } ) => { + await requestUtils.deleteAllTemplates( 'wp_template' ); + } ); + + test( 'woocommerce//* slug is supported', async ( { + admin, + page, + editor, + } ) => { + const template = { + id: 'single-product', + name: 'Single Product', + customText: 'This is a customized template.', + frontendPath: '/product/hoodie/', + }; + + await test.step( 'Customize existing template to create DB entry', async () => { + await admin.visitSiteEditor( { + postId: `woocommerce/woocommerce//${ template.id }`, + postType: 'wp_template', + canvas: 'edit', + } ); + + const title = editor.canvas.getByText( 'Title' ); + + await title.click(); + await title.press( 'Enter' ); + + const emptyBlock = editor.canvas + .getByLabel( 'Empty block' ) + .first(); + + await emptyBlock.fill( template.customText ); + await page.keyboard.press( 'Escape' ); + + await expect( + editor.canvas.getByText( template.customText ) + ).toBeVisible(); + + await editor.saveSiteEditorEntities(); + } ); + + await test.step( 'Update created term to legacy format in the DB', async () => { + const cliOutput = await cli( + `npm run wp-env run tests-cli -- \ + wp term update wp_theme woocommerce-woocommerce \ + --by="slug" \ + --name="woocommerce" \ + --slug="woocommerce"` + ); + + expect( cliOutput.stdout ).toContain( 'Success: Term updated.' ); + } ); + + await test.step( 'Verify the template can be edited via a legacy ID ', async () => { + await admin.visitSiteEditor( { + postId: `woocommerce//${ template.id }`, + postType: 'wp_template', + canvas: 'edit', + } ); + + await expect( + editor.canvas.getByText( template.customText ) + ).toBeVisible(); + } ); + + await test.step( 'Verify the template is listed in the Site Editor UI', async () => { + await admin.visitSiteEditor( { + path: '/wp_template/all', + } ); + + await page.getByPlaceholder( 'Search' ).fill( template.name ); + + await expect( + page.getByRole( 'link', { name: template.name } ) + ).toBeVisible(); + } ); + + await test.step( 'Verify the template loads correctly in the frontend', async () => { + await page.goto( template.frontendPath ); + + await expect( page.getByText( template.customText ) ).toBeVisible(); + } ); + + await test.step( 'Revert term update', async () => { + const cliOutput = await cli( + `npm run wp-env run tests-cli -- \ + wp term update wp_theme woocommerce \ + --by="slug" \ + --name="woocommerce/woocommerce" \ + --slug="woocommerce-woocommerce"` + ); + + expect( cliOutput.stdout ).toContain( 'Success: Term updated.' ); + } ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/order-confirmation.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/order-confirmation.block_theme.spec.ts index 4adef316079f3..7d2d21834aeec 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/templates/order-confirmation.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/order-confirmation.block_theme.spec.ts @@ -3,7 +3,7 @@ */ import { test, expect } from '@woocommerce/e2e-playwright-utils'; -test.describe( 'Test the order confirmation template', async () => { +test.describe( 'Test the order confirmation template', () => { test( 'Template can be opened in the site editor', async ( { page, editorUtils, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/shop-page.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/shop-page.block_theme.spec.ts new file mode 100644 index 0000000000000..4a9650550bf72 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/shop-page.block_theme.spec.ts @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import { test, expect } from '@woocommerce/e2e-playwright-utils'; +import { cli } from '@woocommerce/e2e-utils'; + +test.describe( 'Shop page', () => { + test( 'template selector is not visible in the Page editor', async ( { + admin, + page, + } ) => { + // Get Shop page ID. + const cliOutput = await cli( + `npm run wp-env run tests-cli -- wp option get woocommerce_shop_page_id` + ); + const numberMatch = cliOutput.stdout.match( /\d+/ ); + // eslint-disable-next-line playwright/no-conditional-in-test + if ( numberMatch === null ) { + throw new Error( 'Shop page ID not found' ); + } + + await admin.editPost( numberMatch[ 0 ] ); + + await expect( page.getByText( 'Template' ) ).toBeHidden(); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/single-product-template.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/single-product-template.block_theme.spec.ts index e901142d1d578..8683feadf3cf2 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/templates/single-product-template.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/single-product-template.block_theme.spec.ts @@ -3,11 +3,7 @@ */ import { test, expect } from '@woocommerce/e2e-playwright-utils'; -test.describe( 'Single Product template', async () => { - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - } ); - +test.describe( 'Single Product template', () => { test( 'loads the Single Product template for a specific product', async ( { admin, editor, @@ -24,10 +20,7 @@ test.describe( 'Single Product template', async () => { const userText = 'Hello World in the Belt template'; // Create the specific product template. - await admin.visitAdminPage( - 'site-editor.php', - `path=/${ testData.templateType }` - ); + await admin.visitSiteEditor( { path: `/${ testData.templateType }` } ); await page.getByLabel( 'Add New Template' ).click(); await page .getByRole( 'button', { name: 'Single item: Product' } ) @@ -52,10 +45,9 @@ test.describe( 'Single Product template', async () => { await expect( page.getByText( userText ).first() ).toBeVisible(); // Revert edition. - await admin.visitAdminPage( - 'site-editor.php', - `path=/${ testData.templateType }/all` - ); + await admin.visitSiteEditor( { + path: `/${ testData.templateType }/all`, + } ); await editorUtils.revertTemplateCreation( testData.templateName ); await page.goto( testData.permalink ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/single-product-template.block_theme_with_templates.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/single-product-template.block_theme_with_templates.spec.ts index 3f1764d812a31..c6fc2cbf97e24 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/templates/single-product-template.block_theme_with_templates.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/single-product-template.block_theme_with_templates.spec.ts @@ -18,11 +18,7 @@ const testData = { const userText = 'Hello World in the Belt template'; const themeTemplateText = 'Single Product Belt template loaded from theme'; -test.describe( 'Single Product Template', async () => { - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( 'wp_template' ); - } ); - +test.describe( 'Single Product Template', () => { test( 'loads the theme template for a specific product using the product slug and it can be customized', async ( { admin, editor, @@ -48,10 +44,9 @@ test.describe( 'Single Product Template', async () => { await expect( page.getByText( userText ).first() ).toBeVisible(); // Revert edition and verify the template from the theme is used. - await admin.visitAdminPage( - 'site-editor.php', - `path=/${ testData.templateType }/all` - ); + await admin.visitSiteEditor( { + path: `/${ testData.templateType }/all`, + } ); await editorUtils.revertTemplateCustomizations( testData.templateName ); await page.goto( testData.permalink ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme.side_effects.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme.side_effects.spec.ts index 78f347d102901..fef99109dcb74 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme.side_effects.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme.side_effects.spec.ts @@ -20,11 +20,7 @@ for ( const testData of testToRun ) { const userText = `Hello World in the ${ testData.templateName } template`; const woocommerceTemplateUserText = `Hello World in the WooCommerce ${ testData.templateName } template`; - test.describe( `${ testData.templateName } template`, async () => { - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( testData.templateType ); - } ); - + test.describe( `${ testData.templateName } template`, () => { test( `user-modified ${ testData.templateName } template based on the theme template has priority over the user-modified template based on the default WooCommerce template`, async ( { page, admin, @@ -73,10 +69,9 @@ for ( const testData of testToRun ) { // `deleteAllTemplates()`). This way, we verify there are no // duplicate templates with the same name. // See: https://github.com/woocommerce/woocommerce/issues/42220 - await admin.visitAdminPage( - 'site-editor.php', - `path=/${ testData.templateType }/all` - ); + await admin.visitSiteEditor( { + path: `/${ testData.templateType }/all`, + } ); await editorUtils.revertTemplateCustomizations( testData.templateName ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme.spec.ts index d0fa03a5bcc5e..ec878d3cb953f 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme.spec.ts @@ -14,11 +14,7 @@ CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => { const templateTypeName = testData.templateType === 'wp_template' ? 'template' : 'template part'; - test.describe( `${ testData.templateName } template`, async () => { - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( testData.templateType ); - } ); - + test.describe( `${ testData.templateName } template`, () => { test( 'can be modified and reverted', async ( { admin, frontendUtils, @@ -48,10 +44,9 @@ CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => { await expect( page.getByText( userText ).first() ).toBeVisible(); // Verify the edition can be reverted. - await admin.visitAdminPage( - 'site-editor.php', - `path=/${ testData.templateType }/all` - ); + await admin.visitSiteEditor( { + path: `/${ testData.templateType }/all`, + } ); await editorUtils.revertTemplateCustomizations( testData.templateName ); @@ -85,10 +80,9 @@ CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => { ).toBeVisible(); // Verify the edition can be reverted. - await admin.visitAdminPage( - 'site-editor.php', - `path=/${ testData.templateType }/all` - ); + await admin.visitSiteEditor( { + path: `/${ testData.templateType }/all`, + } ); await editorUtils.revertTemplateCustomizations( testData.fallbackTemplate?.templateName || '' ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme_with_templates.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme_with_templates.spec.ts index 756ee9ea726a3..0edd26c84ecbf 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme_with_templates.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/templates/template-customization.block_theme_with_templates.spec.ts @@ -17,11 +17,7 @@ CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => { const templateTypeName = testData.templateType === 'wp_template' ? 'template' : 'template part'; - test.describe( `${ testData.templateName } template`, async () => { - test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllTemplates( testData.templateType ); - } ); - + test.describe( `${ testData.templateName } template`, () => { test( "theme template has priority over WooCommerce's and can be modified", async ( { admin, editor, @@ -52,10 +48,9 @@ CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => { await expect( page.getByText( userText ).first() ).toBeVisible(); // Revert edition and verify the template from the theme is used. - await admin.visitAdminPage( - 'site-editor.php', - `path=/${ testData.templateType }/all` - ); + await admin.visitSiteEditor( { + path: `/${ testData.templateType }/all`, + } ); await editorUtils.revertTemplateCustomizations( testData.templateName ); @@ -97,10 +92,9 @@ CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => { ).toHaveCount( 0 ); // Revert the edit. - await admin.visitAdminPage( - 'site-editor.php', - `path=/${ testData.templateType }/all` - ); + await admin.visitSiteEditor( { + path: `/${ testData.templateType }/all`, + } ); await editorUtils.revertTemplateCustomizations( testData.fallbackTemplate?.templateName || '' ); diff --git a/plugins/woocommerce-blocks/tests/mocks/emptytheme/block-template-parts/footer.html b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/block-template-parts/footer.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/emptytheme/block-template-parts/footer.html rename to plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/block-template-parts/footer.html diff --git a/plugins/woocommerce-blocks/tests/mocks/emptytheme/block-template-parts/header.html b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/block-template-parts/header.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/emptytheme/block-template-parts/header.html rename to plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/block-template-parts/header.html diff --git a/plugins/woocommerce-blocks/tests/mocks/emptytheme/block-templates/index.html b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/block-templates/index.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/emptytheme/block-templates/index.html rename to plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/block-templates/index.html diff --git a/plugins/woocommerce-blocks/tests/mocks/emptytheme/block-templates/singular.html b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/block-templates/singular.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/emptytheme/block-templates/singular.html rename to plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/block-templates/singular.html diff --git a/plugins/woocommerce-blocks/tests/mocks/emptytheme/functions.php b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/functions.php similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/emptytheme/functions.php rename to plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/functions.php diff --git a/plugins/woocommerce-blocks/tests/mocks/emptytheme/index.php b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/index.php similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/emptytheme/index.php rename to plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/index.php diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/screenshot.jpg b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/screenshot.jpg new file mode 100644 index 0000000000000..a73de4dad518c Binary files /dev/null and b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/screenshot.jpg differ diff --git a/plugins/woocommerce-blocks/tests/mocks/emptytheme/style.css b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/style.css similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/emptytheme/style.css rename to plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/style.css diff --git a/plugins/woocommerce-blocks/tests/mocks/emptytheme/styles/variation.json b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/styles/variation.json similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/emptytheme/styles/variation.json rename to plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/styles/variation.json diff --git a/plugins/woocommerce-blocks/tests/mocks/emptytheme/theme.json b/plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/theme.json similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/emptytheme/theme.json rename to plugins/woocommerce-blocks/tests/e2e/themes/emptytheme/theme.json diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-filter/functions.php b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-filter/functions.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-filter/functions.php rename to plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-filter/functions.php diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-filter/screenshot.jpg b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-filter/screenshot.jpg new file mode 100644 index 0000000000000..a73de4dad518c Binary files /dev/null and b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-filter/screenshot.jpg differ diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-filter/style.css b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-filter/style.css new file mode 100644 index 0000000000000..0225eb0899d47 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-filter/style.css @@ -0,0 +1,13 @@ +/** + * Theme Name: Storefront Child (block notices by filter) + * Theme URI: https://github.com/woocommerce/woocommerce/ + * Template: storefront + * Description: Storefront child theme to test block notices by filter. + * Version: 1.0 + * Author: WooCommerce + * Author URI: https://woo.com + * Requires at least: 5.3 + * Requires PHP: 5.6 + * License: GNU General Public License v2.0 or later + * License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/screenshot.jpg b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/screenshot.jpg new file mode 100644 index 0000000000000..a73de4dad518c Binary files /dev/null and b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/screenshot.jpg differ diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/style.css b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/style.css new file mode 100644 index 0000000000000..0dcb75dfbf6d8 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/style.css @@ -0,0 +1,13 @@ +/** + * Theme Name: Storefront Child (block notices by template) + * Theme URI: https://github.com/woocommerce/woocommerce/ + * Template: storefront + * Description: Storefront child theme to test block notices by template. + * Version: 1.0 + * Author: WooCommerce + * Author URI: https://woo.com + * Requires at least: 5.3 + * Requires PHP: 5.6 + * License: GNU General Public License v2.0 or later + * License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-template/woocommerce/notices/error.php b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/woocommerce/notices/error.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-template/woocommerce/notices/error.php rename to plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/woocommerce/notices/error.php diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-template/woocommerce/notices/notice.php b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/woocommerce/notices/notice.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-template/woocommerce/notices/notice.php rename to plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/woocommerce/notices/notice.php diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-template/woocommerce/notices/success.php b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/woocommerce/notices/success.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__block-notices-template/woocommerce/notices/success.php rename to plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__block-notices-template/woocommerce/notices/success.php diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/screenshot.jpg b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/screenshot.jpg new file mode 100644 index 0000000000000..a73de4dad518c Binary files /dev/null and b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/screenshot.jpg differ diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/style.css b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/style.css new file mode 100644 index 0000000000000..2f698f9e363c5 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/style.css @@ -0,0 +1,13 @@ +/** + * Theme Name: Storefront Child (classic notices by template) + * Theme URI: https://github.com/woocommerce/woocommerce/ + * Template: storefront + * Description: Storefront child theme to test classic notices by template. + * Version: 1.0 + * Author: WooCommerce + * Author URI: https://woo.com + * Requires at least: 5.3 + * Requires PHP: 5.6 + * License: GNU General Public License v2.0 or later + * License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__classic-notices-template/woocommerce/notices/error.php b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/woocommerce/notices/error.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__classic-notices-template/woocommerce/notices/error.php rename to plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/woocommerce/notices/error.php diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__classic-notices-template/woocommerce/notices/notice.php b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/woocommerce/notices/notice.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__classic-notices-template/woocommerce/notices/notice.php rename to plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/woocommerce/notices/notice.php diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__classic-notices-template/woocommerce/notices/success.php b/plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/woocommerce/notices/success.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/storefront-child__classic-notices-template/woocommerce/notices/success.php rename to plugins/woocommerce-blocks/tests/e2e/themes/storefront-child__classic-notices-template/woocommerce/notices/success.php diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-template-parts/footer.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-template-parts/footer.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-template-parts/footer.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-template-parts/footer.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-template-parts/header.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-template-parts/header.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-template-parts/header.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-template-parts/header.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-template-parts/mini-cart.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-template-parts/mini-cart.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-template-parts/mini-cart.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-template-parts/mini-cart.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/archive-product.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/archive-product.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/archive-product.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/archive-product.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/index.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/index.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/index.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/index.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/order-confirmation.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/order-confirmation.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/order-confirmation.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/order-confirmation.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/page-cart.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/page-cart.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/page-cart.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/page-cart.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/page-checkout.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/page-checkout.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/page-checkout.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/page-checkout.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/product-search-results.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/product-search-results.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/product-search-results.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/product-search-results.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/single-product-belt.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/single-product-belt.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/single-product-belt.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/single-product-belt.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/single-product.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/single-product.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/single-product.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/single-product.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/singular.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/singular.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/singular.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/singular.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_attribute.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/taxonomy-product_attribute.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_attribute.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/taxonomy-product_attribute.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_cat.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/taxonomy-product_cat.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_cat.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/taxonomy-product_cat.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_tag.html b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/taxonomy-product_tag.html similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_tag.html rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/block-templates/taxonomy-product_tag.html diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/functions.php b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/functions.php similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/functions.php rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/functions.php diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/index.php b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/index.php similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/index.php rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/index.php diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/screenshot.jpg b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/screenshot.jpg new file mode 100644 index 0000000000000..a73de4dad518c Binary files /dev/null and b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/screenshot.jpg differ diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/style.css b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/style.css similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/style.css rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/style.css diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/styles/variation.json b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/styles/variation.json similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/styles/variation.json rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/styles/variation.json diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/theme.json b/plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/theme.json similarity index 100% rename from plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/theme.json rename to plugins/woocommerce-blocks/tests/e2e/themes/theme-with-woo-templates/theme.json diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-filter/functions.php b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-filter/functions.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-filter/functions.php rename to plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-filter/functions.php diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-filter/screenshot.jpg b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-filter/screenshot.jpg new file mode 100644 index 0000000000000..a73de4dad518c Binary files /dev/null and b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-filter/screenshot.jpg differ diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-filter/style.css b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-filter/style.css new file mode 100644 index 0000000000000..0fdf1617f55bc --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-filter/style.css @@ -0,0 +1,13 @@ +/** + * Theme Name: Twenty Twenty-Four Child (block notices by filter) + * Theme URI: https://github.com/woocommerce/woocommerce/ + * Template: twentytwentyfour + * Description: Twenty Twenty-Four child theme to test block notices by filter. + * Version: 1.0 + * Author: WooCommerce + * Author URI: https://woo.com + * Requires at least: 5.3 + * Requires PHP: 5.6 + * License: GNU General Public License v2.0 or later + * License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/screenshot.jpg b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/screenshot.jpg new file mode 100644 index 0000000000000..a73de4dad518c Binary files /dev/null and b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/screenshot.jpg differ diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/style.css b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/style.css new file mode 100644 index 0000000000000..14bf2c3369fbb --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/style.css @@ -0,0 +1,13 @@ +/** + * Theme Name: Twenty Twenty-Four Child (block notices by template) + * Theme URI: https://github.com/woocommerce/woocommerce/ + * Template: twentytwentyfour + * Description: Twenty Twenty-Four child theme to test block notices by template. + * Version: 1.0 + * Author: WooCommerce + * Author URI: https://woo.com + * Requires at least: 5.3 + * Requires PHP: 5.6 + * License: GNU General Public License v2.0 or later + * License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/error.php b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/error.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/error.php rename to plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/error.php diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/notice.php b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/notice.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/notice.php rename to plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/notice.php diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/success.php b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/success.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/success.php rename to plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__block-notices-template/woocommerce/notices/success.php diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/screenshot.jpg b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/screenshot.jpg new file mode 100644 index 0000000000000..a73de4dad518c Binary files /dev/null and b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/screenshot.jpg differ diff --git a/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/style.css b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/style.css new file mode 100644 index 0000000000000..2b1571de05a04 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/style.css @@ -0,0 +1,13 @@ +/** + * Theme Name: Twenty Twenty-Four Child (classic notices by template) + * Theme URI: https://github.com/woocommerce/woocommerce/ + * Template: twentytwentyfour + * Description: Twenty Twenty-Four child theme to test classic notices by template. + * Version: 1.0 + * Author: WooCommerce + * Author URI: https://woo.com + * Requires at least: 5.3 + * Requires PHP: 5.6 + * License: GNU General Public License v2.0 or later + * License URI: https://www.gnu.org/licenses/gpl-2.0.html + */ diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/error.php b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/error.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/error.php rename to plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/error.php diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/notice.php b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/notice.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/notice.php rename to plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/notice.php diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/success.php b/plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/success.php similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/bin/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/success.php rename to plugins/woocommerce-blocks/tests/e2e/themes/twentytwentyfour-child__classic-notices-template/woocommerce/notices/success.php diff --git a/plugins/woocommerce-blocks/tests/e2e/utils/constants.ts b/plugins/woocommerce-blocks/tests/e2e/utils/constants.ts index c084a730ef0c8..6be5530acdb11 100644 --- a/plugins/woocommerce-blocks/tests/e2e/utils/constants.ts +++ b/plugins/woocommerce-blocks/tests/e2e/utils/constants.ts @@ -32,3 +32,5 @@ export const customerFile = path.join( 'customer.json' ); export const guestFile = { cookies: [], origins: [] }; + +export const DB_EXPORT_FILE = 'blocks_e2e.sql'; diff --git a/plugins/woocommerce-blocks/tests/e2e/utils/editor/editor-utils.page.ts b/plugins/woocommerce-blocks/tests/e2e/utils/editor/editor-utils.page.ts index 3b95e64def5eb..b989740b8cbc7 100644 --- a/plugins/woocommerce-blocks/tests/e2e/utils/editor/editor-utils.page.ts +++ b/plugins/woocommerce-blocks/tests/e2e/utils/editor/editor-utils.page.ts @@ -2,7 +2,7 @@ * External dependencies */ import { Page } from '@playwright/test'; -import { Editor } from '@wordpress/e2e-test-utils-playwright'; +import { Editor, expect, Admin } from '@wordpress/e2e-test-utils-playwright'; import { BlockRepresentation } from '@wordpress/e2e-test-utils-playwright/build-types/editor/insert-block'; /** @@ -13,9 +13,11 @@ import type { TemplateType } from '../../utils/types'; export class EditorUtils { editor: Editor; page: Page; - constructor( editor: Editor, page: Page ) { + admin: Admin; + constructor( editor: Editor, page: Page, admin: Admin ) { this.editor = editor; this.page = page; + this.admin = admin; } /** @@ -124,6 +126,17 @@ export class EditorUtils { ); } + async removeBlockByClientId( clientId: string ) { + await this.page.evaluate( + ( { clientId: _clientId } ) => { + window.wp.data + .dispatch( 'core/block-editor' ) + .removeBlocks( _clientId ); + }, + { clientId } + ); + } + async closeModalByName( name: string ) { const isModalOpen = await this.page.getByLabel( name ).isVisible(); @@ -297,22 +310,44 @@ export class EditorUtils { } async closeWelcomeGuideModal() { - const isModalOpen = await this.page - .getByRole( 'dialog', { name: 'Welcome to the site editor' } ) - .locator( 'div' ) - .filter( { - hasText: - 'Edit your siteDesign everything on your site — from the header right down to the', - } ) - .nth( 2 ) - .isVisible(); + await this.page.waitForFunction( () => { + return ( + window.wp && + window.wp.data && + window.wp.data.dispatch( 'core/preferences' ) + ); + } ); - // eslint-disable-next-line playwright/no-conditional-in-test - if ( isModalOpen ) { - await this.page - .getByRole( 'button', { name: 'Get started' } ) - .click(); - } + // Disable the welcome guide for the site editor. + await this.page.evaluate( () => { + return Promise.all( [ + window.wp.data + .dispatch( 'core/preferences' ) + .set( 'core/edit-site', 'welcomeGuide', false ), + window.wp.data + .dispatch( 'core/preferences' ) + .set( 'core/edit-site', 'welcomeGuideStyles', false ), + window.wp.data + .dispatch( 'core/preferences' ) + .set( 'core/edit-site', 'welcomeGuidePage', false ), + window.wp.data + .dispatch( 'core/preferences' ) + .set( 'core/edit-site', 'welcomeGuideTemplate', false ), + window.wp.data + .dispatch( 'core/preferences' ) + .set( 'core/edit-post', 'welcomeGuide', false ), + window.wp.data + .dispatch( 'core/preferences' ) + .set( 'core/edit-post', 'welcomeGuideStyles', false ), + window.wp.data + .dispatch( 'core/preferences' ) + .set( 'core/edit-post', 'welcomeGuidePage', false ), + + window.wp.data + .dispatch( 'core/preferences' ) + .set( 'core/edit-post', 'welcomeGuideTemplate', false ), + ] ); + } ); } async transformIntoBlocks() { @@ -372,6 +407,9 @@ export class EditorUtils { templateType: TemplateType ) { if ( templateType === 'wp_template_part' ) { + await this.admin.visitSiteEditor( { + path: `/${ templateType }/all`, + } ); await this.page.goto( `/wp-admin/site-editor.php?path=/${ templateType }/all` ); @@ -381,9 +419,9 @@ export class EditorUtils { } ); await templateLink.click(); } else { - await this.page.goto( - `/wp-admin/site-editor.php?path=/${ templateType }` - ); + await this.admin.visitSiteEditor( { + path: '/' + templateType, + } ); const templateButton = this.page.getByRole( 'button', { name: templateName, exact: true, @@ -392,7 +430,6 @@ export class EditorUtils { } await this.enterEditMode(); - await this.closeWelcomeGuideModal(); await this.waitForSiteEditorFinishLoading(); // Verify we are editing the correct template and it has the correct title. @@ -406,6 +443,8 @@ export class EditorUtils { } async revertTemplateCreation( templateName: string ) { + await this.page.getByPlaceholder( 'Search' ).fill( templateName ); + const templateRow = this.page.getByRole( 'row' ).filter( { has: this.page.getByRole( 'link', { name: templateName, @@ -425,27 +464,24 @@ export class EditorUtils { } async revertTemplateCustomizations( templateName: string ) { + await this.page.getByPlaceholder( 'Search' ).fill( templateName ); + const templateRow = this.page.getByRole( 'row' ).filter( { has: this.page.getByRole( 'link', { name: templateName, exact: true, } ), } ); - const resetButton = templateRow.getByRole( 'button', { - name: 'Reset', - } ); - const waitForReset = this.page + const resetButton = templateRow.getByLabel( 'Reset', { exact: true } ); + const revertedNotice = this.page .getByLabel( 'Dismiss this notice' ) - .getByText( `"${ templateName }" reverted.` ) - .waitFor(); - - const waitForSavedLabel = this.page - .getByRole( 'button', { name: 'Saved' } ) - .waitFor(); + .getByText( `"${ templateName }" reverted.` ); + const savedButton = this.page.getByRole( 'button', { name: 'Saved' } ); await resetButton.click(); - await waitForSavedLabel; - await waitForReset; + + await expect( revertedNotice ).toBeVisible(); + await expect( savedButton ).toBeVisible(); } async updatePost() { @@ -496,7 +532,7 @@ export class EditorUtils { productSlug: string, createIfDoesntExist = true ) { - await this.page.goto( '/wp-admin/site-editor.php' ); + await this.admin.visitSiteEditor(); await this.page.getByRole( 'button', { name: 'Templates' } ).click(); const templateButton = this.page.getByRole( 'button', { diff --git a/plugins/woocommerce-blocks/tests/e2e/utils/index.ts b/plugins/woocommerce-blocks/tests/e2e/utils/index.ts index 2ff29f8aa2ea1..26441a300c1cf 100644 --- a/plugins/woocommerce-blocks/tests/e2e/utils/index.ts +++ b/plugins/woocommerce-blocks/tests/e2e/utils/index.ts @@ -8,5 +8,4 @@ export * from './mini-cart'; export * from './performance'; export * from './shipping'; export * from './storeApi'; -export * from './use-block-theme'; export * from './wpCli'; diff --git a/plugins/woocommerce-blocks/tests/e2e/utils/use-block-theme.ts b/plugins/woocommerce-blocks/tests/e2e/utils/use-block-theme.ts deleted file mode 100644 index 09ec81fd53206..0000000000000 --- a/plugins/woocommerce-blocks/tests/e2e/utils/use-block-theme.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * External dependencies - */ -import { test as Test } from '@wordpress/e2e-test-utils-playwright'; - -/** - * Internal dependencies - */ -import { BLOCK_THEME_SLUG } from './constants'; - -export const useBlockTheme = ( test: typeof Test ) => { - test.beforeAll( async ( { requestUtils } ) => { - await requestUtils.activateTheme( BLOCK_THEME_SLUG ); - } ); -}; diff --git a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/screenshot.png b/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/screenshot.png deleted file mode 100644 index 2772d1bc9f8ae..0000000000000 Binary files a/plugins/woocommerce-blocks/tests/mocks/theme-with-woo-templates/screenshot.png and /dev/null differ diff --git a/plugins/woocommerce-blocks/tests/utils/merchant.ts b/plugins/woocommerce-blocks/tests/utils/merchant.ts index 98ae4ab7b9599..b6edf8d0ec538 100644 --- a/plugins/woocommerce-blocks/tests/utils/merchant.ts +++ b/plugins/woocommerce-blocks/tests/utils/merchant.ts @@ -46,7 +46,7 @@ export const merchant = { await expect( page ).toFill( 'input[name="local_pickup_title"]', - 'Local Pickup' + 'Pickup' ); await merchant.saveLocalPickupSettingsPageWithRefresh(); }, diff --git a/plugins/woocommerce/.eslintignore b/plugins/woocommerce/.eslintignore index dfd656285dbf9..4332c035a1065 100644 --- a/plugins/woocommerce/.eslintignore +++ b/plugins/woocommerce/.eslintignore @@ -1,20 +1,27 @@ *.min.js -/assets/js/accounting/** -/assets/js/flexslider/** -/assets/js/jquery-blockui/** -/assets/js/jquery-cookie/** -/assets/js/jquery-flot/** -/assets/js/jquery-payment/** -/assets/js/jquery-qrcode/** -/assets/js/jquery-serializejson/** -/assets/js/jquery-tiptip/** -/assets/js/jquery-ui-touch-punch/** -/assets/js/js-cookie/** -/assets/js/photoswipe/** -/assets/js/prettyPhoto/** -/assets/js/round/** -/assets/js/select2/** -/assets/js/selectWoo/** -/assets/js/stupidtable/** -/assets/js/zoom/** \ No newline at end of file +/.wireit/** +/assets/** +/bin/composer/** +/client/legacy/build/** +/client/legacy/js/accounting/** +/client/legacy/js/flexslider/** +/client/legacy/js/jquery-blockui/** +/client/legacy/js/jquery-cookie/** +/client/legacy/js/jquery-flot/** +/client/legacy/js/jquery-payment/** +/client/legacy/js/jquery-qrcode/** +/client/legacy/js/jquery-serializejson/** +/client/legacy/js/jquery-tiptip/** +/client/legacy/js/jquery-ui-touch-punch/** +/client/legacy/js/js-cookie/** +/client/legacy/js/photoswipe/** +/client/legacy/js/prettyPhoto/** +/client/legacy/js/round/** +/client/legacy/js/select2/** +/client/legacy/js/selectWoo/** +/client/legacy/js/stupidtable/** +/client/legacy/js/zoom/** +/includes/gateways/** +/tests/e2e/** +/vendor/** diff --git a/plugins/woocommerce/.eslintrc.js b/plugins/woocommerce/.eslintrc.js index a5b4e437b5f9d..63bb844f74512 100644 --- a/plugins/woocommerce/.eslintrc.js +++ b/plugins/woocommerce/.eslintrc.js @@ -1,5 +1,3 @@ -/** @format */ - module.exports = { env: { browser: true, diff --git a/plugins/woocommerce/.wp-env.json b/plugins/woocommerce/.wp-env.json index facd8d5849906..bc65ab54f71d5 100644 --- a/plugins/woocommerce/.wp-env.json +++ b/plugins/woocommerce/.wp-env.json @@ -30,9 +30,7 @@ "https://github.com/WP-API/Basic-Auth/archive/master.zip", "https://downloads.wordpress.org/plugin/wp-mail-logging.zip" ], - "themes": [ - "https://downloads.wordpress.org/theme/twentynineteen.zip" - ], + "themes": [], "config": { "WP_TESTS_DOMAIN": "localhost", "ALTERNATE_WP_CRON": false diff --git a/plugins/woocommerce/assets/fonts/Inter-VariableFont_slnt,wght.woff2 b/plugins/woocommerce/assets/fonts/Inter-VariableFont_slnt,wght.woff2 new file mode 100644 index 0000000000000..350bbbcf81caa Binary files /dev/null and b/plugins/woocommerce/assets/fonts/Inter-VariableFont_slnt,wght.woff2 differ diff --git a/plugins/woocommerce/assets/fonts/cardo_normal_400.woff2 b/plugins/woocommerce/assets/fonts/cardo_normal_400.woff2 new file mode 100644 index 0000000000000..536d7a570e8a1 Binary files /dev/null and b/plugins/woocommerce/assets/fonts/cardo_normal_400.woff2 differ diff --git a/plugins/woocommerce/bin/eslint-branch.sh b/plugins/woocommerce/bin/eslint-branch.sh new file mode 100644 index 0000000000000..08dc7d2cb9fed --- /dev/null +++ b/plugins/woocommerce/bin/eslint-branch.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Lint branch +# +# Runs eslint, comparing the current branch to its "base" or "parent" branch. +# The base branch defaults to trunk, but another branch name can be specified as an +# optional positional argument. +# +# Example: +# ./eslint-branch.sh base-branch + +baseBranch=${1:-"trunk"} + +# shellcheck disable=SC2046 +changedFiles=$(git diff $(git merge-base HEAD $baseBranch) --relative --name-only --diff-filter=d -- '*.js' '*.ts' '*.tsx') + +# Only complete this if changed files are detected. +if [[ -z $changedFiles ]]; then + echo "No changed files detected." + exit 0 +fi + +# shellcheck disable=SC2086 +pnpm eslint $changedFiles diff --git a/plugins/woocommerce/bin/package-update-textdomain.js b/plugins/woocommerce/bin/package-update-textdomain.js index 0cc5fc65d4e64..d24c801f63fc0 100755 --- a/plugins/woocommerce/bin/package-update-textdomain.js +++ b/plugins/woocommerce/bin/package-update-textdomain.js @@ -20,6 +20,6 @@ wpTextdomain( 'packages/**/*.php', { '_nx:1,2,4c,5d', '_n_noop:1,2,3d', '_nx_noop:1,2,3c,4d', - 'wp_set_script_translations:1,2d,3' + 'wp_set_script_translations:1,2d,3', ], } ); diff --git a/plugins/woocommerce/changelog/2024-04-22-04-49-18-213839 b/plugins/woocommerce/changelog/2024-04-22-04-49-18-213839 new file mode 100644 index 0000000000000..683eaa7fc9985 --- /dev/null +++ b/plugins/woocommerce/changelog/2024-04-22-04-49-18-213839 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix registration of plugin on woocommerce.com if plugin is already active on site. diff --git a/plugins/woocommerce/changelog/43267-add-auto-insert-customer-block b/plugins/woocommerce/changelog/43267-add-auto-insert-customer-block deleted file mode 100644 index 8950a7ac72da0..0000000000000 --- a/plugins/woocommerce/changelog/43267-add-auto-insert-customer-block +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add the Customer Account block to the header automatically on approved themes (including TT4). \ No newline at end of file diff --git a/plugins/woocommerce/changelog/44593-add-wp-env-6.5 b/plugins/woocommerce/changelog/44593-add-wp-env-6.5 deleted file mode 100644 index e57f59698e33e..0000000000000 --- a/plugins/woocommerce/changelog/44593-add-wp-env-6.5 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: Fix E2E tests on WordPress 6.5 - diff --git a/plugins/woocommerce/changelog/45187-product-collection-telemetry-choosing-collection b/plugins/woocommerce/changelog/45187-product-collection-telemetry-choosing-collection deleted file mode 100644 index e27f1de28a640..0000000000000 --- a/plugins/woocommerce/changelog/45187-product-collection-telemetry-choosing-collection +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Product Collection: track collection being chosen in Product Collection diff --git a/plugins/woocommerce/changelog/45356-45341-add-intro-e2e-tests b/plugins/woocommerce/changelog/45356-45341-add-intro-e2e-tests deleted file mode 100644 index 83b43c014fcc6..0000000000000 --- a/plugins/woocommerce/changelog/45356-45341-add-intro-e2e-tests +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -[CYS - E2E tests] Add E2E tests for the intro screen. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45530-fix-45528-single-product-block-restricting-inner-blocks-placement b/plugins/woocommerce/changelog/45530-fix-45528-single-product-block-restricting-inner-blocks-placement deleted file mode 100644 index 8cf88ded5672d..0000000000000 --- a/plugins/woocommerce/changelog/45530-fix-45528-single-product-block-restricting-inner-blocks-placement +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix issue preventing some blocks from being direct children of the Single Product block. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45551-fix-43967-43960-43890 b/plugins/woocommerce/changelog/45551-fix-43967-43960-43890 deleted file mode 100644 index 8dd5cbb4cb712..0000000000000 --- a/plugins/woocommerce/changelog/45551-fix-43967-43960-43890 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: Update `husky`, add @playwright/test and github-label-sync to syncpack - diff --git a/plugins/woocommerce/changelog/45607-try-double-quotes-css-scss b/plugins/woocommerce/changelog/45607-try-double-quotes-css-scss deleted file mode 100644 index 6259ebeb17188..0000000000000 --- a/plugins/woocommerce/changelog/45607-try-double-quotes-css-scss +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix -Comment: Update stylelint configurations for linting to match `@wordpress` packages we consume. - diff --git a/plugins/woocommerce/changelog/45655-add-42046-CheckboxControl-e2e-tests b/plugins/woocommerce/changelog/45655-add-42046-CheckboxControl-e2e-tests deleted file mode 100644 index 57516bfce8dac..0000000000000 --- a/plugins/woocommerce/changelog/45655-add-42046-CheckboxControl-e2e-tests +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Add an e2e test to ensure that each component has a unique ID. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45767-update-order-summary-layout b/plugins/woocommerce/changelog/45767-update-order-summary-layout new file mode 100644 index 0000000000000..fb1c2253d6c72 --- /dev/null +++ b/plugins/woocommerce/changelog/45767-update-order-summary-layout @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Update the order summary on Cart & Checkout with some minor visual changes diff --git a/plugins/woocommerce/changelog/45773-add-coming-soon-page b/plugins/woocommerce/changelog/45773-add-coming-soon-page deleted file mode 100644 index bfac22e1ae60c..0000000000000 --- a/plugins/woocommerce/changelog/45773-add-coming-soon-page +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add coming soon page and its page selector \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45778-fix-44893-shipping-cost-task-input-layout-issue b/plugins/woocommerce/changelog/45778-fix-44893-shipping-cost-task-input-layout-issue deleted file mode 100644 index 55a2d9806ff9d..0000000000000 --- a/plugins/woocommerce/changelog/45778-fix-44893-shipping-cost-task-input-layout-issue +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix input layout issue with shipping task in Firefox. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45781-fix-query-non-public-tax b/plugins/woocommerce/changelog/45781-fix-query-non-public-tax deleted file mode 100644 index e22119a37bef7..0000000000000 --- a/plugins/woocommerce/changelog/45781-fix-query-non-public-tax +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Product Collection: Don't show publicly non-queryable taxonomies \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45791-tweak-45790 b/plugins/woocommerce/changelog/45791-tweak-45790 deleted file mode 100644 index 4304e135d986d..0000000000000 --- a/plugins/woocommerce/changelog/45791-tweak-45790 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: tweak - -WC_Discount: Add a filter for the items to apply coupons array. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45804-45343-cys-core-e2e-tests-add-coverage-fot-the-loading-page b/plugins/woocommerce/changelog/45804-45343-cys-core-e2e-tests-add-coverage-fot-the-loading-page deleted file mode 100644 index e501aeb2cffa2..0000000000000 --- a/plugins/woocommerce/changelog/45804-45343-cys-core-e2e-tests-add-coverage-fot-the-loading-page +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: [CYS - E2E tests] Add E2E tests for the loading screen - diff --git a/plugins/woocommerce/changelog/45824-add-44400-e2e-test-for-digital-store b/plugins/woocommerce/changelog/45824-add-44400-e2e-test-for-digital-store deleted file mode 100644 index cb494831cb718..0000000000000 --- a/plugins/woocommerce/changelog/45824-add-44400-e2e-test-for-digital-store +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -Add e2e tests for virtual orders \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45828-fix-44405-disable-shipping-logic b/plugins/woocommerce/changelog/45828-fix-44405-disable-shipping-logic deleted file mode 100644 index 84e86e9ff6fb6..0000000000000 --- a/plugins/woocommerce/changelog/45828-fix-44405-disable-shipping-logic +++ /dev/null @@ -1,4 +0,0 @@ -Significance: major -Type: fix - -Fix a bug that prevented placing an order when shipping is disabled, but Local Pickup is still enabled. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45830-add-patterns-title-spacing b/plugins/woocommerce/changelog/45830-add-patterns-title-spacing deleted file mode 100644 index 616898c1d1f2a..0000000000000 --- a/plugins/woocommerce/changelog/45830-add-patterns-title-spacing +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Increase the spacing between the title and the rest of the pattern for: "Product Gallery", "Product Collection: Featured Products 5 Columns" and "Testimonials 3 Columns". \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45855-test-42171-add-e2e-tests-for-product-filter-price-block b/plugins/woocommerce/changelog/45855-test-42171-add-e2e-tests-for-product-filter-price-block deleted file mode 100644 index dbb82416f50b8..0000000000000 --- a/plugins/woocommerce/changelog/45855-test-42171-add-e2e-tests-for-product-filter-price-block +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Add E2E tests for the Product Filter: Price block \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45859-dev-attempt-vendoring b/plugins/woocommerce/changelog/45859-dev-attempt-vendoring deleted file mode 100644 index 98764c638a5a1..0000000000000 --- a/plugins/woocommerce/changelog/45859-dev-attempt-vendoring +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: performance - -Introduce vendor bundling to the blocks cart and checkout pages to improve performance. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45862-patch-4 b/plugins/woocommerce/changelog/45862-patch-4 deleted file mode 100644 index 2eed12190218d..0000000000000 --- a/plugins/woocommerce/changelog/45862-patch-4 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update -Comment: No changelog needed, this is just a docs update - diff --git a/plugins/woocommerce/changelog/45894-45350-transitional-page-e2e-tests b/plugins/woocommerce/changelog/45894-45350-transitional-page-e2e-tests deleted file mode 100644 index c00dc459a450c..0000000000000 --- a/plugins/woocommerce/changelog/45894-45350-transitional-page-e2e-tests +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -[CYS - E2E tests] Add E2E tests for the transitional screen. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45915-update-45904-lys-provide-private-url b/plugins/woocommerce/changelog/45915-update-45904-lys-provide-private-url deleted file mode 100644 index 8aef1a2c08509..0000000000000 --- a/plugins/woocommerce/changelog/45915-update-45904-lys-provide-private-url +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Add private link with copy link functionality \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45926-add-e2e-color-picker b/plugins/woocommerce/changelog/45926-add-e2e-color-picker deleted file mode 100644 index ee0b55cb58a94..0000000000000 --- a/plugins/woocommerce/changelog/45926-add-e2e-color-picker +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: CYS - E2E tests: Add color picker E2E tests - diff --git a/plugins/woocommerce/changelog/45931-fix-45842-product-collection-block-hand-picked-items-limited-to-first-100-in-catalog b/plugins/woocommerce/changelog/45931-fix-45842-product-collection-block-hand-picked-items-limited-to-first-100-in-catalog deleted file mode 100644 index 18734f0323089..0000000000000 --- a/plugins/woocommerce/changelog/45931-fix-45842-product-collection-block-hand-picked-items-limited-to-first-100-in-catalog +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Fix: Hand-picked control only allow selection from first 100 products \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45932-update-lys-task-action-url b/plugins/woocommerce/changelog/45932-update-lys-task-action-url deleted file mode 100644 index 4f0381937594c..0000000000000 --- a/plugins/woocommerce/changelog/45932-update-lys-task-action-url +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Update Launch Your Store task action URL \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45933-redirect-to-intro-when-going-to-transitional-but-no-theme-mods b/plugins/woocommerce/changelog/45933-redirect-to-intro-when-going-to-transitional-but-no-theme-mods deleted file mode 100644 index f27a595858d75..0000000000000 --- a/plugins/woocommerce/changelog/45933-redirect-to-intro-when-going-to-transitional-but-no-theme-mods +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Redirect to the CYS intro screen when accessing the transitional page without going through the customizing process. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45934-remove-customer-account-block-margin-left b/plugins/woocommerce/changelog/45934-remove-customer-account-block-margin-left deleted file mode 100644 index 256a17077a8d9..0000000000000 --- a/plugins/woocommerce/changelog/45934-remove-customer-account-block-margin-left +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak - -Removes unnecessary margin from Customer Account block label. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45959-add-e2e-font-picker b/plugins/woocommerce/changelog/45959-add-e2e-font-picker deleted file mode 100644 index 0e7d398d1140b..0000000000000 --- a/plugins/woocommerce/changelog/45959-add-e2e-font-picker +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: CYS - E2E tests: Add font picker E2E tests - diff --git a/plugins/woocommerce/changelog/45961-fix-41527-prevent-duplicate-orders-being-placed b/plugins/woocommerce/changelog/45961-fix-41527-prevent-duplicate-orders-being-placed deleted file mode 100644 index 27369ecb6e7dd..0000000000000 --- a/plugins/woocommerce/changelog/45961-fix-41527-prevent-duplicate-orders-being-placed +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: enhancement -Comment: Changed shopper feedback on payment response failure - diff --git a/plugins/woocommerce/changelog/45974-dev-introduce-ariakit-button b/plugins/woocommerce/changelog/45974-dev-introduce-ariakit-button new file mode 100644 index 0000000000000..d07e43d25d654 --- /dev/null +++ b/plugins/woocommerce/changelog/45974-dev-introduce-ariakit-button @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +In blocks migrate `@wordpress/components` Button to Ariakit, replace `__experimentalRadio/RadioGroup` with Ariakit Button. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45979-frosso-patch-1 b/plugins/woocommerce/changelog/45979-frosso-patch-1 deleted file mode 100644 index a85211a922053..0000000000000 --- a/plugins/woocommerce/changelog/45979-frosso-patch-1 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak - -refactor: `woocommerce_rest_checkout_process_payment_error` returns a `400` response code, instead of `402`. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45984-45344-e2e-tests-cys-footer b/plugins/woocommerce/changelog/45984-45344-e2e-tests-cys-footer deleted file mode 100644 index 78b7debbae749..0000000000000 --- a/plugins/woocommerce/changelog/45984-45344-e2e-tests-cys-footer +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -CYS - E2E tests: Add footer section E2E tests \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45998-product-collection-post-title-link-disable b/plugins/woocommerce/changelog/45998-product-collection-post-title-link-disable deleted file mode 100644 index 05316c5af8d12..0000000000000 --- a/plugins/woocommerce/changelog/45998-product-collection-post-title-link-disable +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Remove pointer cursor and underline on hover for the product collection product title in the editor \ No newline at end of file diff --git a/plugins/woocommerce/changelog/45999-add-e2e-add-logo-cys b/plugins/woocommerce/changelog/45999-add-e2e-add-logo-cys deleted file mode 100644 index 5f67965318068..0000000000000 --- a/plugins/woocommerce/changelog/45999-add-e2e-add-logo-cys +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: CYS - E2E tests: add logo picker E2E tests - diff --git a/plugins/woocommerce/changelog/46003-update-remove-duplicated-tos-acceptance b/plugins/woocommerce/changelog/46003-update-remove-duplicated-tos-acceptance deleted file mode 100644 index cbb0b4301b6ed..0000000000000 --- a/plugins/woocommerce/changelog/46003-update-remove-duplicated-tos-acceptance +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Remove ToS acceptance where unnecessary \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46011-45344-e2e-tests-cys-header b/plugins/woocommerce/changelog/46011-45344-e2e-tests-cys-header deleted file mode 100644 index ceea47e5ad127..0000000000000 --- a/plugins/woocommerce/changelog/46011-45344-e2e-tests-cys-header +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -CYS - E2E tests: Add header section E2E tests \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46022-update-41534-adjust-shipping-calculation-text b/plugins/woocommerce/changelog/46022-update-41534-adjust-shipping-calculation-text deleted file mode 100644 index 62c861e4a7226..0000000000000 --- a/plugins/woocommerce/changelog/46022-update-41534-adjust-shipping-calculation-text +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update -Comment: This PR contains a minor textual update. - diff --git a/plugins/woocommerce/changelog/46030-44514-flaky-customize-storeassembler-hubspecjs b/plugins/woocommerce/changelog/46030-44514-flaky-customize-storeassembler-hubspecjs deleted file mode 100644 index 6b360cacb9759..0000000000000 --- a/plugins/woocommerce/changelog/46030-44514-flaky-customize-storeassembler-hubspecjs +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: CYS - E2E tests: fix flaky assembler-hub test - diff --git a/plugins/woocommerce/changelog/46053-fix-update-rc-version b/plugins/woocommerce/changelog/46053-fix-update-rc-version deleted file mode 100644 index a6340560898e2..0000000000000 --- a/plugins/woocommerce/changelog/46053-fix-update-rc-version +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: [Blocks - E2E] Update WordPress version - diff --git a/plugins/woocommerce/changelog/46078-add-45873-track-site-visibility-action b/plugins/woocommerce/changelog/46078-add-45873-track-site-visibility-action deleted file mode 100644 index 39cfaf93b9260..0000000000000 --- a/plugins/woocommerce/changelog/46078-add-45873-track-site-visibility-action +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Add tracks for site visibility settings \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46083-update-46013-shipping-section-changes b/plugins/woocommerce/changelog/46083-update-46013-shipping-section-changes new file mode 100644 index 0000000000000..12c9ecf94df29 --- /dev/null +++ b/plugins/woocommerce/changelog/46083-update-46013-shipping-section-changes @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update delivery titles & pickup options; refine shipping selector layout & address field visibility in the Checkout block. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46093-fix-current-screen-error-with-lys b/plugins/woocommerce/changelog/46093-fix-current-screen-error-with-lys deleted file mode 100644 index 3bbd960e1e879..0000000000000 --- a/plugins/woocommerce/changelog/46093-fix-current-screen-error-with-lys +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix undefined error with current_screen in lys class. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46095-update-46075-settings-copy-changes-lys b/plugins/woocommerce/changelog/46095-update-46075-settings-copy-changes-lys deleted file mode 100644 index 9f4ce83d061d3..0000000000000 --- a/plugins/woocommerce/changelog/46095-update-46075-settings-copy-changes-lys +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Update LYS site visibility settings copies. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46096-update-46073-front-end-footer-banner-for-coming-soon-mode b/plugins/woocommerce/changelog/46096-update-46073-front-end-footer-banner-for-coming-soon-mode deleted file mode 100644 index 9cf111adf0e8a..0000000000000 --- a/plugins/woocommerce/changelog/46096-update-46073-front-end-footer-banner-for-coming-soon-mode +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Add coming soon banner on the frontend when coming soon mode is enabled. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46097-update-46071-copy-changes-for-homescreen-lys-badge b/plugins/woocommerce/changelog/46097-update-46071-copy-changes-for-homescreen-lys-badge deleted file mode 100644 index 3803f515d96ec..0000000000000 --- a/plugins/woocommerce/changelog/46097-update-46071-copy-changes-for-homescreen-lys-badge +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Copy text update for LYS homescreen badge. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46098-update-46072-add-icions-to-ellipsis-menu-lys-home-badge b/plugins/woocommerce/changelog/46098-update-46072-add-icions-to-ellipsis-menu-lys-home-badge deleted file mode 100644 index c542b74230198..0000000000000 --- a/plugins/woocommerce/changelog/46098-update-46072-add-icions-to-ellipsis-menu-lys-home-badge +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Adds cog and edit icons to homescreen LYS status pill \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46101-add-lys-coming-soon-templates b/plugins/woocommerce/changelog/46101-add-lys-coming-soon-templates deleted file mode 100644 index d44024ba4c1f9..0000000000000 --- a/plugins/woocommerce/changelog/46101-add-lys-coming-soon-templates +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update -Comment: Update unreleased feature - diff --git a/plugins/woocommerce/changelog/46109-dev-disable-xstate-in-prod-builds b/plugins/woocommerce/changelog/46109-dev-disable-xstate-in-prod-builds deleted file mode 100644 index 733398b633461..0000000000000 --- a/plugins/woocommerce/changelog/46109-dev-disable-xstate-in-prod-builds +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Fixes a bug introduced previously where enabling localStorage.xstateV5_inspect would cause the page to crash because it's not supposed to be used in prod builds. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46125-remove-e2e-side-effects b/plugins/woocommerce/changelog/46125-remove-e2e-side-effects new file mode 100644 index 0000000000000..1f49d04944eca --- /dev/null +++ b/plugins/woocommerce/changelog/46125-remove-e2e-side-effects @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Block E2E: Eliminate side effects through improved test isolation diff --git a/plugins/woocommerce/changelog/46127-add-e2e-homepage b/plugins/woocommerce/changelog/46127-add-e2e-homepage deleted file mode 100644 index 71c7c89132a49..0000000000000 --- a/plugins/woocommerce/changelog/46127-add-e2e-homepage +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: CYS - E2E tests: add homepage picker E2E tests - diff --git a/plugins/woocommerce/changelog/46140-update-46133-site-visibility-tab b/plugins/woocommerce/changelog/46140-update-46133-site-visibility-tab deleted file mode 100644 index b7eea1b32bfbd..0000000000000 --- a/plugins/woocommerce/changelog/46140-update-46133-site-visibility-tab +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Move site visibility settings to a new tab \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46155-45893-e2e-blocks-remove-hardcoded-wordpress-version-in-wp-envjson-after-wordpress-65-has-been-released b/plugins/woocommerce/changelog/46155-45893-e2e-blocks-remove-hardcoded-wordpress-version-in-wp-envjson-after-wordpress-65-has-been-released deleted file mode 100644 index c171089fe2469..0000000000000 --- a/plugins/woocommerce/changelog/46155-45893-e2e-blocks-remove-hardcoded-wordpress-version-in-wp-envjson-after-wordpress-65-has-been-released +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: E2E test env: use WordPress 6.5 stable version - diff --git a/plugins/woocommerce/changelog/46156-product-collection-reset-all-resets-also-the-collection-filters b/plugins/woocommerce/changelog/46156-product-collection-reset-all-resets-also-the-collection-filters deleted file mode 100644 index abf8c8c034bb9..0000000000000 --- a/plugins/woocommerce/changelog/46156-product-collection-reset-all-resets-also-the-collection-filters +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Product Collection: Fix the "Reset All" funtionality in Editor filters diff --git a/plugins/woocommerce/changelog/46170-fix-featured-category-triple-overlay b/plugins/woocommerce/changelog/46170-fix-featured-category-triple-overlay deleted file mode 100644 index 67e8491a60a20..0000000000000 --- a/plugins/woocommerce/changelog/46170-fix-featured-category-triple-overlay +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Fix the overlay color of the "Featured Category Triple" pattern. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46188-fix-skip-color-picker-e2e-test b/plugins/woocommerce/changelog/46188-fix-skip-color-picker-e2e-test deleted file mode 100644 index 8269f677f6d39..0000000000000 --- a/plugins/woocommerce/changelog/46188-fix-skip-color-picker-e2e-test +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: Skip color picker E2E tests - diff --git a/plugins/woocommerce/changelog/46203-update-46070-lys-tour b/plugins/woocommerce/changelog/46203-update-46070-lys-tour deleted file mode 100644 index c3f16781b6f88..0000000000000 --- a/plugins/woocommerce/changelog/46203-update-46070-lys-tour +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add a tour for the homescreen site status badge. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46204-update-46185-copy-change-for-coming-soon-page-setup b/plugins/woocommerce/changelog/46204-update-46185-copy-change-for-coming-soon-page-setup deleted file mode 100644 index 640481cdd7b38..0000000000000 --- a/plugins/woocommerce/changelog/46204-update-46185-copy-change-for-coming-soon-page-setup +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Copy change for the coming soon label in advanced setting. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46207-add-lys-coming-soon-templates-layout b/plugins/woocommerce/changelog/46207-add-lys-coming-soon-templates-layout deleted file mode 100644 index 67cc9cbfa67ad..0000000000000 --- a/plugins/woocommerce/changelog/46207-add-lys-coming-soon-templates-layout +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update -Comment: Expands unreleased feature - diff --git a/plugins/woocommerce/changelog/46212-46194-cys-investigate-potential-bad-performance-for-the-color-palette b/plugins/woocommerce/changelog/46212-46194-cys-investigate-potential-bad-performance-for-the-color-palette deleted file mode 100644 index 1b379c48c7697..0000000000000 --- a/plugins/woocommerce/changelog/46212-46194-cys-investigate-potential-bad-performance-for-the-color-palette +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix -Comment: CYS: add debounce to improve performance - diff --git a/plugins/woocommerce/changelog/46216-fix-color-palette-button-colors b/plugins/woocommerce/changelog/46216-fix-color-palette-button-colors deleted file mode 100644 index 20fee40de565f..0000000000000 --- a/plugins/woocommerce/changelog/46216-fix-color-palette-button-colors +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -[CYS] - Fix color inconsistencies in the color palette buttons. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46217-disable-header-test b/plugins/woocommerce/changelog/46217-disable-header-test deleted file mode 100644 index ea8ba59642334..0000000000000 --- a/plugins/woocommerce/changelog/46217-disable-header-test +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Skip CYS header test. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46219-dev-delete-base-tabs-component b/plugins/woocommerce/changelog/46219-dev-delete-base-tabs-component deleted file mode 100644 index 0bca412defdd3..0000000000000 --- a/plugins/woocommerce/changelog/46219-dev-delete-base-tabs-component +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: Remove the unused WooCommerce blocks Tabs component, remove Reakit dependency. - diff --git a/plugins/woocommerce/changelog/46220-dev-remove-product-add-to-cart-block b/plugins/woocommerce/changelog/46220-dev-remove-product-add-to-cart-block deleted file mode 100644 index e4e21a61b2cae..0000000000000 --- a/plugins/woocommerce/changelog/46220-dev-remove-product-add-to-cart-block +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: Remove experimental unused product add to cart block, don't tree-shake `@woocommerce/types` to avoid runtime exception. - diff --git a/plugins/woocommerce/changelog/46221-fix-header-tests b/plugins/woocommerce/changelog/46221-fix-header-tests deleted file mode 100644 index 21496ccd8da50..0000000000000 --- a/plugins/woocommerce/changelog/46221-fix-header-tests +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -[CYS] Update footer and header test to not use snapshots. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46227-46193-cys-color-picker-no-gradient-to-choose-when-gutenberg-latest-is-active b/plugins/woocommerce/changelog/46227-46193-cys-color-picker-no-gradient-to-choose-when-gutenberg-latest-is-active deleted file mode 100644 index 1816476047291..0000000000000 --- a/plugins/woocommerce/changelog/46227-46193-cys-color-picker-no-gradient-to-choose-when-gutenberg-latest-is-active +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix -Comment: CYS - color picker: fix CSS - diff --git a/plugins/woocommerce/changelog/46242-fix-broken-hpos-tests b/plugins/woocommerce/changelog/46242-fix-broken-hpos-tests deleted file mode 100644 index 6097a5927592f..0000000000000 --- a/plugins/woocommerce/changelog/46242-fix-broken-hpos-tests +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Fix tests that were failing against HPOS environment setup. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46258-dev-fix-e2e-additional-fields b/plugins/woocommerce/changelog/46258-dev-fix-e2e-additional-fields deleted file mode 100644 index 708fa8ebafed1..0000000000000 --- a/plugins/woocommerce/changelog/46258-dev-fix-e2e-additional-fields +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: Fix blocks e2e test for additional-fields.merchant.block_theme.side_effects.spec.ts - diff --git a/plugins/woocommerce/changelog/46268-fix-restore-default-theme-cys b/plugins/woocommerce/changelog/46268-fix-restore-default-theme-cys deleted file mode 100644 index 68aef60384e4e..0000000000000 --- a/plugins/woocommerce/changelog/46268-fix-restore-default-theme-cys +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: CYS - E2E tests: restore the default theme - diff --git a/plugins/woocommerce/changelog/46321-update-hide-banner-from-preview b/plugins/woocommerce/changelog/46321-update-hide-banner-from-preview deleted file mode 100644 index 4cb9f0fe9f9c2..0000000000000 --- a/plugins/woocommerce/changelog/46321-update-hide-banner-from-preview +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Hide coming soon banner from LYS preview frame \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46322-fix-lys-badge-dropdown b/plugins/woocommerce/changelog/46322-fix-lys-badge-dropdown deleted file mode 100644 index bae594a466ca9..0000000000000 --- a/plugins/woocommerce/changelog/46322-fix-lys-badge-dropdown +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Update background color and padding in WooCommerce LYS status popover \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46333-fix-improve-color-picker-e2e-tests b/plugins/woocommerce/changelog/46333-fix-improve-color-picker-e2e-tests deleted file mode 100644 index 3fd10eb05b03d..0000000000000 --- a/plugins/woocommerce/changelog/46333-fix-improve-color-picker-e2e-tests +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev -Comment: CYS - E2E test: not use snapshot approach for color picker E2E tests - diff --git a/plugins/woocommerce/changelog/46353-update-admin-header-icons b/plugins/woocommerce/changelog/46353-update-admin-header-icons deleted file mode 100644 index de6aa2caefb84..0000000000000 --- a/plugins/woocommerce/changelog/46353-update-admin-header-icons +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Update WC Admin Homescreen header icons \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46479-blocks-reference-documentation b/plugins/woocommerce/changelog/46479-blocks-reference-documentation new file mode 100644 index 0000000000000..e3daa4dd4e7a8 --- /dev/null +++ b/plugins/woocommerce/changelog/46479-blocks-reference-documentation @@ -0,0 +1,4 @@ +Significance: patch +Type: add +Comment: Adds autogeneration of our block list references. + diff --git a/plugins/woocommerce/changelog/46576-fix-migrate-to-pw b/plugins/woocommerce/changelog/46576-fix-migrate-to-pw new file mode 100644 index 0000000000000..076edaf50eced --- /dev/null +++ b/plugins/woocommerce/changelog/46576-fix-migrate-to-pw @@ -0,0 +1,4 @@ +Significance: patch +Type: dev +Comment: Migrate Price filter tests to Playwright + diff --git a/plugins/woocommerce/changelog/46580-try-filter-product-by-stock-migration b/plugins/woocommerce/changelog/46580-try-filter-product-by-stock-migration new file mode 100644 index 0000000000000..e65cf09cff7e3 --- /dev/null +++ b/plugins/woocommerce/changelog/46580-try-filter-product-by-stock-migration @@ -0,0 +1,4 @@ +Significance: patch +Type: dev +Comment: Migrate Stock filter tests to Playwright + diff --git a/plugins/woocommerce/changelog/46583-try-filter-product-by-rating b/plugins/woocommerce/changelog/46583-try-filter-product-by-rating new file mode 100644 index 0000000000000..74601fe957fa6 --- /dev/null +++ b/plugins/woocommerce/changelog/46583-try-filter-product-by-rating @@ -0,0 +1,4 @@ +Significance: patch +Type: dev +Comment: Migrate Rating filter tests to Playwright + diff --git a/plugins/woocommerce/changelog/46591-try-attribute-filter-migration b/plugins/woocommerce/changelog/46591-try-attribute-filter-migration new file mode 100644 index 0000000000000..24c92c2fc3bfe --- /dev/null +++ b/plugins/woocommerce/changelog/46591-try-attribute-filter-migration @@ -0,0 +1,4 @@ +Significance: patch +Type: dev +Comment: Migrate Attribute filter tests to Playwright + diff --git a/packages/js/components/changelog/42860-update-pnpm b/plugins/woocommerce/changelog/46626-fix-eslint-errors similarity index 53% rename from packages/js/components/changelog/42860-update-pnpm rename to plugins/woocommerce/changelog/46626-fix-eslint-errors index 69a57db741bd1..4d05e41b7907a 100644 --- a/packages/js/components/changelog/42860-update-pnpm +++ b/plugins/woocommerce/changelog/46626-fix-eslint-errors @@ -1,4 +1,4 @@ Significance: patch Type: dev -Comment: Updated PNPM +Comment: Fix ESLint errors diff --git a/plugins/woocommerce/changelog/46644-fix-migration-to-pw-pt-1 b/plugins/woocommerce/changelog/46644-fix-migration-to-pw-pt-1 new file mode 100644 index 0000000000000..c0226f280cc14 --- /dev/null +++ b/plugins/woocommerce/changelog/46644-fix-migration-to-pw-pt-1 @@ -0,0 +1,4 @@ +Significance: patch +Type: dev +Comment: Migrate Jest Puppeteer E2E tests to Playwright. + diff --git a/plugins/woocommerce/changelog/46664-fix-46623-restrict-only-does-not-work-on-a-sub-dir-site b/plugins/woocommerce/changelog/46664-fix-46623-restrict-only-does-not-work-on-a-sub-dir-site new file mode 100644 index 0000000000000..37ab58d3fe80e --- /dev/null +++ b/plugins/woocommerce/changelog/46664-fix-46623-restrict-only-does-not-work-on-a-sub-dir-site @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +FIxed a bug where shop page isn't recognized as a WooCommerce page when WordPress is installed in a subdirectory with permalink set to plain. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46671-fix-migration-to-pw-pt2 b/plugins/woocommerce/changelog/46671-fix-migration-to-pw-pt2 new file mode 100644 index 0000000000000..c0226f280cc14 --- /dev/null +++ b/plugins/woocommerce/changelog/46671-fix-migration-to-pw-pt2 @@ -0,0 +1,4 @@ +Significance: patch +Type: dev +Comment: Migrate Jest Puppeteer E2E tests to Playwright. + diff --git a/plugins/woocommerce/changelog/46692-fix-86639-delete-order-item-meta-query-hyperdb b/plugins/woocommerce/changelog/46692-fix-86639-delete-order-item-meta-query-hyperdb new file mode 100644 index 0000000000000..bf9757fd9b32b --- /dev/null +++ b/plugins/woocommerce/changelog/46692-fix-86639-delete-order-item-meta-query-hyperdb @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Update delete item meta query to format supported by wpdb::get_table_from_query() \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46711-dev-update-webpack-blocks b/plugins/woocommerce/changelog/46711-dev-update-webpack-blocks new file mode 100644 index 0000000000000..73a7a03b9e572 --- /dev/null +++ b/plugins/woocommerce/changelog/46711-dev-update-webpack-blocks @@ -0,0 +1,4 @@ +Significance: patch +Type: dev +Comment: Update Webpack to 5.91.0 in Blocks + diff --git a/plugins/woocommerce/changelog/46763-dev-46759-wc-product-pre-has-unique-sku b/plugins/woocommerce/changelog/46763-dev-46759-wc-product-pre-has-unique-sku new file mode 100644 index 0000000000000..1bbcebdfa2b90 --- /dev/null +++ b/plugins/woocommerce/changelog/46763-dev-46759-wc-product-pre-has-unique-sku @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Added the `wc_product_pre_has_unique_sku` filter hook to allow SKU uniqueness to be determined externally \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46785-fix-product-details-use-block-props b/plugins/woocommerce/changelog/46785-fix-product-details-use-block-props new file mode 100644 index 0000000000000..c13d3d692cfa7 --- /dev/null +++ b/plugins/woocommerce/changelog/46785-fix-product-details-use-block-props @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak +Comment: Product Details: not use `useBlockProps` twice + diff --git a/plugins/woocommerce/changelog/46843-cleaning b/plugins/woocommerce/changelog/46843-cleaning new file mode 100644 index 0000000000000..09245a9dea072 --- /dev/null +++ b/plugins/woocommerce/changelog/46843-cleaning @@ -0,0 +1,4 @@ +Significance: minor +Type: tweak + +Remove unused order type registration property. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46854-update-LYS-coming-soon-block-cleanup b/plugins/woocommerce/changelog/46854-update-LYS-coming-soon-block-cleanup new file mode 100644 index 0000000000000..4dd2985914605 --- /dev/null +++ b/plugins/woocommerce/changelog/46854-update-LYS-coming-soon-block-cleanup @@ -0,0 +1,4 @@ +Significance: patch +Type: fix +Comment: update to previously unreleased feature + diff --git a/plugins/woocommerce/changelog/46872-46839-fix-error-on-same-prompt b/plugins/woocommerce/changelog/46872-46839-fix-error-on-same-prompt new file mode 100644 index 0000000000000..c5a630d3c44a6 --- /dev/null +++ b/plugins/woocommerce/changelog/46872-46839-fix-error-on-same-prompt @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +[CYS] Fix bug making the AI flow fail on the same prompt. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46897-dev-fix-a11y-add-to-cart b/plugins/woocommerce/changelog/46897-dev-fix-a11y-add-to-cart new file mode 100644 index 0000000000000..2d203b1afb702 --- /dev/null +++ b/plugins/woocommerce/changelog/46897-dev-fix-a11y-add-to-cart @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fix an accessibility error in the add to cart button template. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46898-update-LYS-status-typescript b/plugins/woocommerce/changelog/46898-update-LYS-status-typescript new file mode 100644 index 0000000000000..6ca757de2f547 --- /dev/null +++ b/plugins/woocommerce/changelog/46898-update-LYS-status-typescript @@ -0,0 +1,4 @@ +Significance: patch +Type: update +Comment: updates previously unreleased feature + diff --git a/plugins/woocommerce/changelog/46899-dev-fix-a11y-icon-only-customer-account-block b/plugins/woocommerce/changelog/46899-dev-fix-a11y-icon-only-customer-account-block new file mode 100644 index 0000000000000..13f1e9a9b0cd5 --- /dev/null +++ b/plugins/woocommerce/changelog/46899-dev-fix-a11y-icon-only-customer-account-block @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Add aria-label to customer account block link when in icon-only display mode. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46900-manual_update-parameter-in-api-request b/plugins/woocommerce/changelog/46900-manual_update-parameter-in-api-request new file mode 100644 index 0000000000000..f2063097c420e --- /dev/null +++ b/plugins/woocommerce/changelog/46900-manual_update-parameter-in-api-request @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Added a "manual_update" parameter to the Orders REST API endpoint that will make it so that, when set to "true", status changes to an order will be attributed to a specific user in the order notes. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46906-update-woocommerce-version-on-status-page b/plugins/woocommerce/changelog/46906-update-woocommerce-version-on-status-page new file mode 100644 index 0000000000000..8cb3119adec02 --- /dev/null +++ b/plugins/woocommerce/changelog/46906-update-woocommerce-version-on-status-page @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update the WooCommerce Status page to use the full plugin version to show dev, Beta and RC versions as opposed to only the milestone number \ No newline at end of file diff --git a/plugins/woocommerce/changelog/46999-fix-fetch-patterns-ai-data-post-only-if-ai-enabled-and-improve-performance b/plugins/woocommerce/changelog/46999-fix-fetch-patterns-ai-data-post-only-if-ai-enabled-and-improve-performance new file mode 100644 index 0000000000000..d9743ff39130d --- /dev/null +++ b/plugins/woocommerce/changelog/46999-fix-fetch-patterns-ai-data-post-only-if-ai-enabled-and-improve-performance @@ -0,0 +1,4 @@ +Significance: minor +Type: performance + +CYS > Ensure get_patterns_ai_data_post is triggered only if AI enabled and improve performance. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/47010-fix-46993-action-url-link b/plugins/woocommerce/changelog/47010-fix-46993-action-url-link new file mode 100644 index 0000000000000..2ef0ecbd96ee5 --- /dev/null +++ b/plugins/woocommerce/changelog/47010-fix-46993-action-url-link @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix `admin_url` usage in Task List button links for Customize Store and Launch Your Store tasks. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/47073-fix-coming-soon-assets-404 b/plugins/woocommerce/changelog/47073-fix-coming-soon-assets-404 new file mode 100644 index 0000000000000..3db6a7c10c4aa --- /dev/null +++ b/plugins/woocommerce/changelog/47073-fix-coming-soon-assets-404 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix failed to load coming-soon resources \ No newline at end of file diff --git a/plugins/woocommerce/changelog/47080-update-46746-relocate-helper-themes b/plugins/woocommerce/changelog/47080-update-46746-relocate-helper-themes new file mode 100644 index 0000000000000..ab1b3c78f0075 --- /dev/null +++ b/plugins/woocommerce/changelog/47080-update-46746-relocate-helper-themes @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Load e2e test helper (child) themes via .wp-json instead of via WP-CLI. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/47091-skip-writing-to-file-if-text-empty-string-literal b/plugins/woocommerce/changelog/47091-skip-writing-to-file-if-text-empty-string-literal new file mode 100644 index 0000000000000..57d9ff5eb209d --- /dev/null +++ b/plugins/woocommerce/changelog/47091-skip-writing-to-file-if-text-empty-string-literal @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Avoid writing an empty line to a log file if the log entry is empty. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/47136-fix-product-reviews-use-block-props b/plugins/woocommerce/changelog/47136-fix-product-reviews-use-block-props new file mode 100644 index 0000000000000..8a2893507c10a --- /dev/null +++ b/plugins/woocommerce/changelog/47136-fix-product-reviews-use-block-props @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak +Comment: Product Reviews: not use useBlockProps twice + diff --git a/plugins/woocommerce/changelog/47157-dev-fix-47138 b/plugins/woocommerce/changelog/47157-dev-fix-47138 new file mode 100644 index 0000000000000..f0470ed6c4b15 --- /dev/null +++ b/plugins/woocommerce/changelog/47157-dev-fix-47138 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix +Comment: Checkout: Fix the rendering of local pickup / shipping buttons in the editor + diff --git a/plugins/woocommerce/changelog/47158-trunk b/plugins/woocommerce/changelog/47158-trunk new file mode 100644 index 0000000000000..d6d3e96ec2e36 --- /dev/null +++ b/plugins/woocommerce/changelog/47158-trunk @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Remove repetitive words \ No newline at end of file diff --git a/plugins/woocommerce/changelog/add-39887_e2e_tests_variations_warning b/plugins/woocommerce/changelog/add-39887_e2e_tests_variations_warning deleted file mode 100644 index 2e95658c1fbb4..0000000000000 --- a/plugins/woocommerce/changelog/add-39887_e2e_tests_variations_warning +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add E2E tests for product variation notices diff --git a/plugins/woocommerce/changelog/add-44761 b/plugins/woocommerce/changelog/add-44761 deleted file mode 100644 index 6b49f9a3c0cdc..0000000000000 --- a/plugins/woocommerce/changelog/add-44761 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Disable the fields that are not required in variable products because they are set in each variation diff --git a/plugins/woocommerce/changelog/add-46020_e2e_tests_for_linked_products b/plugins/woocommerce/changelog/add-46020_e2e_tests_for_linked_products deleted file mode 100644 index 795ea06c8c503..0000000000000 --- a/plugins/woocommerce/changelog/add-46020_e2e_tests_for_linked_products +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add e2e tests for linked products #46024 diff --git a/plugins/woocommerce/changelog/add-46107_e2e_for_analytics_product_segmentation b/plugins/woocommerce/changelog/add-46107_e2e_for_analytics_product_segmentation deleted file mode 100644 index 4370bba1d41ce..0000000000000 --- a/plugins/woocommerce/changelog/add-46107_e2e_for_analytics_product_segmentation +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Add E2E test for Analytics products segmentation filter. diff --git a/plugins/woocommerce/changelog/add-category-placeholder b/plugins/woocommerce/changelog/add-category-placeholder new file mode 100644 index 0000000000000..6f15ef257e230 --- /dev/null +++ b/plugins/woocommerce/changelog/add-category-placeholder @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +New product editor: Add 'placeholder' attribute to category field in Simple Product Template diff --git a/plugins/woocommerce/changelog/add-change-product-elements-category-to-product-elements b/plugins/woocommerce/changelog/add-change-product-elements-category-to-product-elements deleted file mode 100644 index c5be62099d46b..0000000000000 --- a/plugins/woocommerce/changelog/add-change-product-elements-category-to-product-elements +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Product Elements: unify the Product Elements inserter category diff --git a/plugins/woocommerce/changelog/add-coming-soon-editor-link b/plugins/woocommerce/changelog/add-coming-soon-editor-link deleted file mode 100644 index 57fcc8e2840f6..0000000000000 --- a/plugins/woocommerce/changelog/add-coming-soon-editor-link +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -Add coming soon page editor links diff --git a/plugins/woocommerce/changelog/add-e2e_tests_for_grouped_product b/plugins/woocommerce/changelog/add-e2e_tests_for_grouped_product deleted file mode 100644 index d474778ea0c47..0000000000000 --- a/plugins/woocommerce/changelog/add-e2e_tests_for_grouped_product +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add E2E tests for grouped products #45964 diff --git a/plugins/woocommerce/changelog/add-lys-hub-sidebar b/plugins/woocommerce/changelog/add-lys-hub-sidebar deleted file mode 100644 index fa5a84abfabb5..0000000000000 --- a/plugins/woocommerce/changelog/add-lys-hub-sidebar +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Added lys hub sidebar diff --git a/plugins/woocommerce/changelog/add-lys-hub-site-preview b/plugins/woocommerce/changelog/add-lys-hub-site-preview deleted file mode 100644 index cf400dfdd6cf7..0000000000000 --- a/plugins/woocommerce/changelog/add-lys-hub-site-preview +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -Add LYS hub site preview diff --git a/plugins/woocommerce/changelog/add-lys-launch-store-action b/plugins/woocommerce/changelog/add-lys-launch-store-action deleted file mode 100644 index 0d7e542486deb..0000000000000 --- a/plugins/woocommerce/changelog/add-lys-launch-store-action +++ /dev/null @@ -1,5 +0,0 @@ -Significance: minor -Type: add - -Added the action to set the appropriate options when launch store button is clicked in LYS - diff --git a/plugins/woocommerce/changelog/add-lys-success-screen b/plugins/woocommerce/changelog/add-lys-success-screen deleted file mode 100644 index a0d652bfd3182..0000000000000 --- a/plugins/woocommerce/changelog/add-lys-success-screen +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add Launch Your Store success screen diff --git a/plugins/woocommerce/changelog/add-lys-xstate-url-handling b/plugins/woocommerce/changelog/add-lys-xstate-url-handling deleted file mode 100644 index 7bffa03766bf7..0000000000000 --- a/plugins/woocommerce/changelog/add-lys-xstate-url-handling +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Added URL handling for LYS XState pages diff --git a/plugins/woocommerce/changelog/add-metadata_variations b/plugins/woocommerce/changelog/add-metadata_variations deleted file mode 100644 index da4f528e3a8d8..0000000000000 --- a/plugins/woocommerce/changelog/add-metadata_variations +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add meta_data parameter in generate variations endpoint diff --git a/plugins/woocommerce/changelog/add-phone-number-to-order-preview b/plugins/woocommerce/changelog/add-phone-number-to-order-preview deleted file mode 100644 index 1610c6fbb8c8d..0000000000000 --- a/plugins/woocommerce/changelog/add-phone-number-to-order-preview +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -Add shipping phone number in the order preview panel. diff --git a/plugins/woocommerce/changelog/add-product-collection-location-cart-checkout-block-cases b/plugins/woocommerce/changelog/add-product-collection-location-cart-checkout-block-cases deleted file mode 100644 index 38db2e1dfeac5..0000000000000 --- a/plugins/woocommerce/changelog/add-product-collection-location-cart-checkout-block-cases +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Product Collection: recognise if block is inside Cart or Checkout block diff --git a/plugins/woocommerce/changelog/add-store-connect-to-woocom b/plugins/woocommerce/changelog/add-store-connect-to-woocom deleted file mode 100644 index aa82693f86d9c..0000000000000 --- a/plugins/woocommerce/changelog/add-store-connect-to-woocom +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add a new task (connect to WooCommerce.com) in WC onboarding tasklist diff --git a/plugins/woocommerce/changelog/add-test-for-woocommerce_specific_allowed_countries b/plugins/woocommerce/changelog/add-test-for-woocommerce_specific_allowed_countries deleted file mode 100644 index 661912e24a100..0000000000000 --- a/plugins/woocommerce/changelog/add-test-for-woocommerce_specific_allowed_countries +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Add a test for the `woocommerce_specific_allowed_countries` option. diff --git a/plugins/woocommerce/changelog/add-use-state-name-in-address-card b/plugins/woocommerce/changelog/add-use-state-name-in-address-card deleted file mode 100644 index 3daf5932e0ea7..0000000000000 --- a/plugins/woocommerce/changelog/add-use-state-name-in-address-card +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -Use state names in Checkout Block address cards. diff --git a/plugins/woocommerce/changelog/bump-update-wordpress-env-to-9.0.7 b/plugins/woocommerce/changelog/bump-update-wordpress-env-to-9.0.7 deleted file mode 100644 index b4d6ff4e804c5..0000000000000 --- a/plugins/woocommerce/changelog/bump-update-wordpress-env-to-9.0.7 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Update @wordpress-env package to version 9.0.7 diff --git a/plugins/woocommerce/changelog/ci-add-support-for-events-in-ci-jobs b/plugins/woocommerce/changelog/ci-add-support-for-events-in-ci-jobs new file mode 100644 index 0000000000000..24fe8dc2f21da --- /dev/null +++ b/plugins/woocommerce/changelog/ci-add-support-for-events-in-ci-jobs @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Monorepo utils: add support for github events in ci-jobs tool diff --git a/plugins/woocommerce/changelog/dependabot-npm_and_yarn-fast-xml-parser-4.2.5 b/plugins/woocommerce/changelog/dependabot-npm_and_yarn-fast-xml-parser-4.2.5 new file mode 100644 index 0000000000000..27009186c7fa6 --- /dev/null +++ b/plugins/woocommerce/changelog/dependabot-npm_and_yarn-fast-xml-parser-4.2.5 @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Update fast-xml-parser from 4.2.4 to 4.2.5 diff --git a/plugins/woocommerce/changelog/dependabot-npm_and_yarn-octokit-3.1.2 b/plugins/woocommerce/changelog/dependabot-npm_and_yarn-octokit-3.1.2 new file mode 100644 index 0000000000000..f3d250232a401 --- /dev/null +++ b/plugins/woocommerce/changelog/dependabot-npm_and_yarn-octokit-3.1.2 @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update Octokit from 2.1.0 to 3.1.2 and updated variable names diff --git a/plugins/woocommerce/changelog/dev-36777_update_copy_inventory_management b/plugins/woocommerce/changelog/dev-36777_update_copy_inventory_management deleted file mode 100644 index d69255d075b4e..0000000000000 --- a/plugins/woocommerce/changelog/dev-36777_update_copy_inventory_management +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Update copy of inventory management fields #45801 diff --git a/plugins/woocommerce/changelog/dev-43282_create_api_endpoint_duplicate_product b/plugins/woocommerce/changelog/dev-43282_create_api_endpoint_duplicate_product deleted file mode 100644 index 0be2a6c2d1c1e..0000000000000 --- a/plugins/woocommerce/changelog/dev-43282_create_api_endpoint_duplicate_product +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Add API Rest endpoint to duplicate product #46141 diff --git a/plugins/woocommerce/changelog/dev-45921_add_help_text_to_downloads_toggle b/plugins/woocommerce/changelog/dev-45921_add_help_text_to_downloads_toggle new file mode 100644 index 0000000000000..de7e516175cb5 --- /dev/null +++ b/plugins/woocommerce/changelog/dev-45921_add_help_text_to_downloads_toggle @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add help text under "Include downloads" toggle #46752 diff --git a/plugins/woocommerce/changelog/dev-add-expected-version b/plugins/woocommerce/changelog/dev-add-expected-version deleted file mode 100644 index 3a238407fda1e..0000000000000 --- a/plugins/woocommerce/changelog/dev-add-expected-version +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -add the expected template version to template bump warning diff --git a/plugins/woocommerce/changelog/dev-add-legacy-unit-tests-to-ci b/plugins/woocommerce/changelog/dev-add-legacy-unit-tests-to-ci deleted file mode 100644 index a6be4eac37844..0000000000000 --- a/plugins/woocommerce/changelog/dev-add-legacy-unit-tests-to-ci +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: fix the CI unit test path for legacy unit tests - - diff --git a/plugins/woocommerce/changelog/dev-change-lys-task-id b/plugins/woocommerce/changelog/dev-change-lys-task-id deleted file mode 100644 index b6f670aea73b7..0000000000000 --- a/plugins/woocommerce/changelog/dev-change-lys-task-id +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Changed LYS task list task id from underscore to hyphens for consistency with the other tasks \ No newline at end of file diff --git a/plugins/woocommerce/changelog/dev-k6-cot-to-hpos b/plugins/woocommerce/changelog/dev-k6-cot-to-hpos deleted file mode 100644 index 8b82cb9942b2e..0000000000000 --- a/plugins/woocommerce/changelog/dev-k6-cot-to-hpos +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: update cot references in performance tests to hpos - - diff --git a/plugins/woocommerce/changelog/dev-remove-blocks-repo-entry b/plugins/woocommerce/changelog/dev-remove-blocks-repo-entry deleted file mode 100644 index 2d1876ffb6098..0000000000000 --- a/plugins/woocommerce/changelog/dev-remove-blocks-repo-entry +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -cleanup in blocks package.json diff --git a/plugins/woocommerce/changelog/dev-update-support-request-not-planned b/plugins/woocommerce/changelog/dev-update-support-request-not-planned deleted file mode 100644 index e9db5343587d7..0000000000000 --- a/plugins/woocommerce/changelog/dev-update-support-request-not-planned +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Change the support request GH workflow to label issues as not planned when closing them diff --git a/plugins/woocommerce/changelog/dev-xstatev5-inspector b/plugins/woocommerce/changelog/dev-xstatev5-inspector deleted file mode 100644 index b0eb4250d27e1..0000000000000 --- a/plugins/woocommerce/changelog/dev-xstatev5-inspector +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Added xstate v5 inspector functionality diff --git a/plugins/woocommerce/changelog/do-not-assume-megabytes b/plugins/woocommerce/changelog/do-not-assume-megabytes deleted file mode 100644 index f3c847a5c13a4..0000000000000 --- a/plugins/woocommerce/changelog/do-not-assume-megabytes +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix bug parsing memory_limit in product importer diff --git a/plugins/woocommerce/changelog/dont-show-draft-product-in-featured-block b/plugins/woocommerce/changelog/dont-show-draft-product-in-featured-block deleted file mode 100644 index 2e111fc298034..0000000000000 --- a/plugins/woocommerce/changelog/dont-show-draft-product-in-featured-block +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Don't show a product in the featured products block if the status is other than published and the user doesn't have read capability for that product. diff --git a/plugins/woocommerce/changelog/e2e-add-changes-to-test-projects b/plugins/woocommerce/changelog/e2e-add-changes-to-test-projects deleted file mode 100644 index 614993fcc2cb0..0000000000000 --- a/plugins/woocommerce/changelog/e2e-add-changes-to-test-projects +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Add changes to tests projects diff --git a/plugins/woocommerce/changelog/e2e-add-merchant-checkout-block-options b/plugins/woocommerce/changelog/e2e-add-merchant-checkout-block-options deleted file mode 100644 index f6d6c93abeaac..0000000000000 --- a/plugins/woocommerce/changelog/e2e-add-merchant-checkout-block-options +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -E2E tests: add remaining tests to cover merchant checkout block flow / milestone \ No newline at end of file diff --git a/plugins/woocommerce/changelog/e2e-add-merchant-insert-patterns b/plugins/woocommerce/changelog/e2e-add-merchant-insert-patterns deleted file mode 100644 index 001dccd1cd1b1..0000000000000 --- a/plugins/woocommerce/changelog/e2e-add-merchant-insert-patterns +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -E2E tests: add merchant e2e tests to cover inserting WooCoommerce patterns \ No newline at end of file diff --git a/plugins/woocommerce/changelog/e2e-add-merchant-inserting-woo-blocks b/plugins/woocommerce/changelog/e2e-add-merchant-inserting-woo-blocks deleted file mode 100644 index 5141d46573c77..0000000000000 --- a/plugins/woocommerce/changelog/e2e-add-merchant-inserting-woo-blocks +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -E2E tests: add test for merchant insert all woo blocks to page \ No newline at end of file diff --git a/plugins/woocommerce/changelog/e2e-disable-coming-soon-in-env-setup b/plugins/woocommerce/changelog/e2e-disable-coming-soon-in-env-setup new file mode 100644 index 0000000000000..9cd8583567585 --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-disable-coming-soon-in-env-setup @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +E2E tests: disable woocommerce_coming_soon during test environment setup diff --git a/plugins/woocommerce/changelog/e2e-display-marketing-overview b/plugins/woocommerce/changelog/e2e-display-marketing-overview deleted file mode 100644 index 0bc49e9906f34..0000000000000 --- a/plugins/woocommerce/changelog/e2e-display-marketing-overview +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Enhances the e2e tests for the marketing overview page diff --git a/plugins/woocommerce/changelog/e2e-dont-exit-if-cannot-clear-consumer-token b/plugins/woocommerce/changelog/e2e-dont-exit-if-cannot-clear-consumer-token new file mode 100644 index 0000000000000..b65fbd9b66f9e --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-dont-exit-if-cannot-clear-consumer-token @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +E2E tests: don't exit if the consumer token was not cleared in teardown diff --git a/plugins/woocommerce/changelog/e2e-fix-cleanup-for-created-pages b/plugins/woocommerce/changelog/e2e-fix-cleanup-for-created-pages new file mode 100644 index 0000000000000..5baab77e106fc --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-fix-cleanup-for-created-pages @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +E2E tests: fix cleanup of created test pages and migrate to using fixtures diff --git a/plugins/woocommerce/changelog/e2e-fix-create-order-test-fix b/plugins/woocommerce/changelog/e2e-fix-create-order-test-fix deleted file mode 100644 index 95a52a295e285..0000000000000 --- a/plugins/woocommerce/changelog/e2e-fix-create-order-test-fix +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -E2E tests: fix locator in create order tests diff --git a/plugins/woocommerce/changelog/e2e-fix-create-simple-product-fix b/plugins/woocommerce/changelog/e2e-fix-create-simple-product-fix deleted file mode 100644 index a17c7ef1430a3..0000000000000 --- a/plugins/woocommerce/changelog/e2e-fix-create-simple-product-fix +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -E2E tests: fixed flaky product creation test diff --git a/plugins/woocommerce/changelog/e2e-fix-customer-payment-page-fix b/plugins/woocommerce/changelog/e2e-fix-customer-payment-page-fix deleted file mode 100644 index e16a8a1461234..0000000000000 --- a/plugins/woocommerce/changelog/e2e-fix-customer-payment-page-fix +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -E2E tests: make payment page tests more resilient to theme changes diff --git a/plugins/woocommerce/changelog/e2e-fix-fixes-for-gutenberg-active b/plugins/woocommerce/changelog/e2e-fix-fixes-for-gutenberg-active new file mode 100644 index 0000000000000..a95cdaa96fe2f --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-fix-fixes-for-gutenberg-active @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +E2E tests: more fixes for tests with Gutenberg active diff --git a/plugins/woocommerce/changelog/e2e-fix-make-e2e-theme-agnostic b/plugins/woocommerce/changelog/e2e-fix-make-e2e-theme-agnostic deleted file mode 100644 index 11dcb9ca5fb77..0000000000000 --- a/plugins/woocommerce/changelog/e2e-fix-make-e2e-theme-agnostic +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -E2E tests: stabilize more tests diff --git a/plugins/woocommerce/changelog/e2e-flaky-test-fixes b/plugins/woocommerce/changelog/e2e-flaky-test-fixes deleted file mode 100644 index 5d255491ea118..0000000000000 --- a/plugins/woocommerce/changelog/e2e-flaky-test-fixes +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Fix some flaky e2e tests diff --git a/plugins/woocommerce/changelog/e2e-merchant-orders-enhancements b/plugins/woocommerce/changelog/e2e-merchant-orders-enhancements deleted file mode 100644 index b8da0dbaad175..0000000000000 --- a/plugins/woocommerce/changelog/e2e-merchant-orders-enhancements +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Enhance the merchant create order e2e tests diff --git a/plugins/woocommerce/changelog/e2e-merchant-shipping-zones b/plugins/woocommerce/changelog/e2e-merchant-shipping-zones deleted file mode 100644 index a17e95f7a6d87..0000000000000 --- a/plugins/woocommerce/changelog/e2e-merchant-shipping-zones +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Adds tests for merchant shipping methods diff --git a/plugins/woocommerce/changelog/e2e-new-order-email b/plugins/woocommerce/changelog/e2e-new-order-email deleted file mode 100644 index 723ce57cf21b3..0000000000000 --- a/plugins/woocommerce/changelog/e2e-new-order-email +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Enhance merchant new order test diff --git a/plugins/woocommerce/changelog/e2e-update-default-theme b/plugins/woocommerce/changelog/e2e-update-default-theme deleted file mode 100644 index 8afeb7b356e7a..0000000000000 --- a/plugins/woocommerce/changelog/e2e-update-default-theme +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -E2E tests: update the default theme to use twentytwentythree diff --git a/plugins/woocommerce/changelog/e2e-woo-com-connection b/plugins/woocommerce/changelog/e2e-woo-com-connection deleted file mode 100644 index 96e4ff1c1e5d9..0000000000000 --- a/plugins/woocommerce/changelog/e2e-woo-com-connection +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Adds an e2e test to initiate a woo.com connection diff --git a/plugins/woocommerce/changelog/enhancement-46502 b/plugins/woocommerce/changelog/enhancement-46502 new file mode 100644 index 0000000000000..0211290f8d8f1 --- /dev/null +++ b/plugins/woocommerce/changelog/enhancement-46502 @@ -0,0 +1,4 @@ +Significance: minor +Type: performance + +Create a hook to filter the woocommerce blocks that can be registered diff --git a/plugins/woocommerce/changelog/feature-postcode-formatting b/plugins/woocommerce/changelog/feature-postcode-formatting deleted file mode 100644 index c85ee230fb636..0000000000000 --- a/plugins/woocommerce/changelog/feature-postcode-formatting +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Improve formatting for SE, LV, CZ, SK postcodes diff --git a/plugins/woocommerce/changelog/fix-25623 b/plugins/woocommerce/changelog/fix-25623 deleted file mode 100644 index d62ea1afc9d3b..0000000000000 --- a/plugins/woocommerce/changelog/fix-25623 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Prevent reading items with zero order ID to avoid mixups. diff --git a/plugins/woocommerce/changelog/fix-27077-order-editor-coupons-deleted-products b/plugins/woocommerce/changelog/fix-27077-order-editor-coupons-deleted-products deleted file mode 100644 index 980bc32a83858..0000000000000 --- a/plugins/woocommerce/changelog/fix-27077-order-editor-coupons-deleted-products +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Addresses a fatal error that can occur when applying a coupon within the order editor (where one of the products has been deleted). diff --git a/plugins/woocommerce/changelog/fix-27568-refund-endpoint b/plugins/woocommerce/changelog/fix-27568-refund-endpoint new file mode 100644 index 0000000000000..54e8632a15783 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-27568-refund-endpoint @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Adds a wc/v3/refunds REST API endpoint so refunds can be queried collectively, unconnected to their orders diff --git a/plugins/woocommerce/changelog/fix-40157-styled-post-content b/plugins/woocommerce/changelog/fix-40157-styled-post-content deleted file mode 100644 index 860c78452ca29..0000000000000 --- a/plugins/woocommerce/changelog/fix-40157-styled-post-content +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -Adds support for sanitizing styled chunks of HTML (a slight expansion of normal `wp_kses_post` rules). diff --git a/plugins/woocommerce/changelog/fix-41308_home_screen_grey_background b/plugins/woocommerce/changelog/fix-41308_home_screen_grey_background deleted file mode 100644 index 702a5f98add1c..0000000000000 --- a/plugins/woocommerce/changelog/fix-41308_home_screen_grey_background +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Fix Home screen grey background #45895 diff --git a/plugins/woocommerce/changelog/fix-42157 b/plugins/woocommerce/changelog/fix-42157 deleted file mode 100644 index 460820d015c49..0000000000000 --- a/plugins/woocommerce/changelog/fix-42157 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix the broken contract in the StoreAPI and the bug in the price clauses causing the incorrect filter counts. diff --git a/plugins/woocommerce/changelog/fix-42682 b/plugins/woocommerce/changelog/fix-42682 new file mode 100644 index 0000000000000..424d4b24d29b9 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-42682 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix Tax reports not showing correct totals in analytics diff --git a/plugins/woocommerce/changelog/fix-43311-fix-query-id-persistence b/plugins/woocommerce/changelog/fix-43311-fix-query-id-persistence deleted file mode 100644 index 67af6ceddfd9e..0000000000000 --- a/plugins/woocommerce/changelog/fix-43311-fix-query-id-persistence +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Ensure queryId and id uniqueness when duplicating the Product Collection block. diff --git a/plugins/woocommerce/changelog/fix-43879 b/plugins/woocommerce/changelog/fix-43879 deleted file mode 100644 index b59b6c61572fa..0000000000000 --- a/plugins/woocommerce/changelog/fix-43879 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: performance - -Don't load REST API when generating possible routes. diff --git a/plugins/woocommerce/changelog/fix-43964-43910-43896-43893 b/plugins/woocommerce/changelog/fix-43964-43910-43896-43893 deleted file mode 100644 index 285a91427e8f2..0000000000000 --- a/plugins/woocommerce/changelog/fix-43964-43910-43896-43893 +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: Syncing stylelint, classnames, sass-loader package versions - - diff --git a/plugins/woocommerce/changelog/fix-44002 b/plugins/woocommerce/changelog/fix-44002 deleted file mode 100644 index 28890a87a2f3f..0000000000000 --- a/plugins/woocommerce/changelog/fix-44002 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: enhancement - -Various UX improvements in HPOS CLI cleanup tool. diff --git a/plugins/woocommerce/changelog/fix-44080 b/plugins/woocommerce/changelog/fix-44080 deleted file mode 100644 index 8850f92886871..0000000000000 --- a/plugins/woocommerce/changelog/fix-44080 +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: Follow up fixup to unreleased change, no changelog necessary. - - diff --git a/plugins/woocommerce/changelog/fix-45699 b/plugins/woocommerce/changelog/fix-45699 new file mode 100644 index 0000000000000..bf65ee114e1a5 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-45699 @@ -0,0 +1,4 @@ +Significance: minor +Type: performance + +Add index on SKU filed in wc_product_meta_lookup table diff --git a/plugins/woocommerce/changelog/fix-46604 b/plugins/woocommerce/changelog/fix-46604 new file mode 100644 index 0000000000000..c174728e15b81 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-46604 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Make REST order queries involving 'customer' field compatible with HPOS in v2 API. diff --git a/plugins/woocommerce/changelog/fix-add-cache-46806 b/plugins/woocommerce/changelog/fix-add-cache-46806 new file mode 100644 index 0000000000000..334fa04870e86 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-add-cache-46806 @@ -0,0 +1,4 @@ +Significance: patch +Type: performance + +HPOS - Made the query for retrieving meta keys more performant diff --git a/plugins/woocommerce/changelog/fix-additional-fields-validation b/plugins/woocommerce/changelog/fix-additional-fields-validation deleted file mode 100644 index 4021669a52fd6..0000000000000 --- a/plugins/woocommerce/changelog/fix-additional-fields-validation +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Always validate missing additional fields diff --git a/plugins/woocommerce/changelog/fix-address-format-card b/plugins/woocommerce/changelog/fix-address-format-card deleted file mode 100644 index 8fb2fdc834322..0000000000000 --- a/plugins/woocommerce/changelog/fix-address-format-card +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Use the address formats from WC_Countries in the checkout block address card diff --git a/plugins/woocommerce/changelog/fix-core-profiler-email-field-overlapping b/plugins/woocommerce/changelog/fix-core-profiler-email-field-overlapping new file mode 100644 index 0000000000000..b0296c29d01c6 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-core-profiler-email-field-overlapping @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix core profiler email field is not positioned correctly in mobile screens diff --git a/plugins/woocommerce/changelog/fix-customer-account-hooked-block-attributes b/plugins/woocommerce/changelog/fix-customer-account-hooked-block-attributes new file mode 100644 index 0000000000000..8b78e857eab2e --- /dev/null +++ b/plugins/woocommerce/changelog/fix-customer-account-hooked-block-attributes @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Don't automatically insert hooked customer account block into header for sites running less than WP 6.5 diff --git a/plugins/woocommerce/changelog/fix-e2e-test-linked-product-count b/plugins/woocommerce/changelog/fix-e2e-test-linked-product-count deleted file mode 100644 index d8802e0c28077..0000000000000 --- a/plugins/woocommerce/changelog/fix-e2e-test-linked-product-count +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Fix linked product e2e tests #46286 diff --git a/plugins/woocommerce/changelog/fix-flaky-tests-fixes b/plugins/woocommerce/changelog/fix-flaky-tests-fixes deleted file mode 100644 index 66a2dcee410bd..0000000000000 --- a/plugins/woocommerce/changelog/fix-flaky-tests-fixes +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Skip mini cart e2e tests, flaky test fixes diff --git a/plugins/woocommerce/changelog/fix-local-pickup-title-sync b/plugins/woocommerce/changelog/fix-local-pickup-title-sync deleted file mode 100644 index aa8699f8fb338..0000000000000 --- a/plugins/woocommerce/changelog/fix-local-pickup-title-sync +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Prevent PHP warning if local pickup has not been set up in your store diff --git a/plugins/woocommerce/changelog/fix-lys-attempt-using-cover-block-fix b/plugins/woocommerce/changelog/fix-lys-attempt-using-cover-block-fix new file mode 100644 index 0000000000000..ba27b26609fe4 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-lys-attempt-using-cover-block-fix @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Refactor coming soon entire page to wrap under cover block diff --git a/plugins/woocommerce/changelog/fix-lys-default-values-actions b/plugins/woocommerce/changelog/fix-lys-default-values-actions deleted file mode 100644 index dfa5c6784f2fc..0000000000000 --- a/plugins/woocommerce/changelog/fix-lys-default-values-actions +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Move the feature flag check to add_lys_default_values method diff --git a/plugins/woocommerce/changelog/fix-lys-gla-integration b/plugins/woocommerce/changelog/fix-lys-gla-integration new file mode 100644 index 0000000000000..59b2dbf46efcb --- /dev/null +++ b/plugins/woocommerce/changelog/fix-lys-gla-integration @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix GLA site verification with coming soon mode diff --git a/plugins/woocommerce/changelog/fix-lys-incomplete-tasks b/plugins/woocommerce/changelog/fix-lys-incomplete-tasks deleted file mode 100644 index 96ec29fe2a36a..0000000000000 --- a/plugins/woocommerce/changelog/fix-lys-incomplete-tasks +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix the LYS Hub tasklist so that it only shows incomplete tasks and tasks that were recently actioned \ No newline at end of file diff --git a/plugins/woocommerce/changelog/fix-mini-cart-block-merchant-only-once-test b/plugins/woocommerce/changelog/fix-mini-cart-block-merchant-only-once-test new file mode 100644 index 0000000000000..0bd404655cf58 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-mini-cart-block-merchant-only-once-test @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Fix Merchant → Mini Cart in FSE editor can only be inserted once test + + diff --git a/plugins/woocommerce/changelog/fix-missing-shipping-recommendation-task b/plugins/woocommerce/changelog/fix-missing-shipping-recommendation-task new file mode 100644 index 0000000000000..5bf2cc6b28e0b --- /dev/null +++ b/plugins/woocommerce/changelog/fix-missing-shipping-recommendation-task @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix missing shipping-recommendation task diff --git a/plugins/woocommerce/changelog/fix-phpcs-add-WordPress.WP.Capabilities b/plugins/woocommerce/changelog/fix-phpcs-add-WordPress.WP.Capabilities deleted file mode 100644 index 14ad90b4ebf1a..0000000000000 --- a/plugins/woocommerce/changelog/fix-phpcs-add-WordPress.WP.Capabilities +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Add WordPress.WP.Capabilities config to phpcs.xml diff --git a/plugins/woocommerce/changelog/fix-product-editor-mis-alignment b/plugins/woocommerce/changelog/fix-product-editor-mis-alignment deleted file mode 100644 index 526352a969c85..0000000000000 --- a/plugins/woocommerce/changelog/fix-product-editor-mis-alignment +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Product Editor: Fixes a mis-alignment of the form when certain extensions are installed. diff --git a/plugins/woocommerce/changelog/fix-remove-noindex-lys-coming-soon-pages b/plugins/woocommerce/changelog/fix-remove-noindex-lys-coming-soon-pages new file mode 100644 index 0000000000000..d3c2410d3eb44 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-remove-noindex-lys-coming-soon-pages @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Remove noindex robot call from lys coming soon pages diff --git a/plugins/woocommerce/changelog/fix-unable-activate-dependent-plugins b/plugins/woocommerce/changelog/fix-unable-activate-dependent-plugins new file mode 100644 index 0000000000000..99beeb2d39b66 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-unable-activate-dependent-plugins @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add filter to convert WooCommerce slug for plugin dependencies diff --git a/plugins/woocommerce/changelog/fix-wccom-19988-double-bubble b/plugins/woocommerce/changelog/fix-wccom-19988-double-bubble deleted file mode 100644 index 2dbef04589fce..0000000000000 --- a/plugins/woocommerce/changelog/fix-wccom-19988-double-bubble +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: Fixing minor bug with display of menu item bubble in marketplace. - - diff --git a/plugins/woocommerce/changelog/hpos-large-guide b/plugins/woocommerce/changelog/hpos-large-guide deleted file mode 100644 index 1ef0cdf598638..0000000000000 --- a/plugins/woocommerce/changelog/hpos-large-guide +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: Added documentation, no functional change. - - diff --git a/plugins/woocommerce/changelog/imp-46153-report-readibility b/plugins/woocommerce/changelog/imp-46153-report-readibility new file mode 100644 index 0000000000000..dbd6df30b73a0 --- /dev/null +++ b/plugins/woocommerce/changelog/imp-46153-report-readibility @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Improved readability and better UX for GitHub bug reports, by optimizing the format of the SSR diff --git a/plugins/woocommerce/changelog/perf-hpos-search b/plugins/woocommerce/changelog/perf-hpos-search deleted file mode 100644 index a919164f26efe..0000000000000 --- a/plugins/woocommerce/changelog/perf-hpos-search +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: enhancement - -Add filters to support adding custom search methods in HPOS admin and remember the last used search option diff --git a/plugins/woocommerce/changelog/perf-indexes b/plugins/woocommerce/changelog/perf-indexes new file mode 100644 index 0000000000000..0aebc3734e763 --- /dev/null +++ b/plugins/woocommerce/changelog/perf-indexes @@ -0,0 +1,4 @@ +Significance: minor +Type: performance + +Add experimental support for FTS indexes in HPOS. Additionally, revert existing HPOS search queries to use post like structure. diff --git a/plugins/woocommerce/changelog/performance-update-get-template-paths b/plugins/woocommerce/changelog/performance-update-get-template-paths deleted file mode 100644 index bc739d28af9a0..0000000000000 --- a/plugins/woocommerce/changelog/performance-update-get-template-paths +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: performance - -Apply upstream performance improvement to _get_templates_paths diff --git a/plugins/woocommerce/changelog/pr-46674 b/plugins/woocommerce/changelog/pr-46674 new file mode 100644 index 0000000000000..56a06ac7ea557 --- /dev/null +++ b/plugins/woocommerce/changelog/pr-46674 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Prevent product from being saved prematurely when updated via REST API \ No newline at end of file diff --git a/plugins/woocommerce/changelog/pr-46975 b/plugins/woocommerce/changelog/pr-46975 new file mode 100644 index 0000000000000..14af47efdf646 --- /dev/null +++ b/plugins/woocommerce/changelog/pr-46975 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Catch NotFoundException before woocommerce_get_batch_processor \ No newline at end of file diff --git a/plugins/woocommerce/changelog/re-44879 b/plugins/woocommerce/changelog/re-44879 deleted file mode 100644 index b7f513e274696..0000000000000 --- a/plugins/woocommerce/changelog/re-44879 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: performance - -[Performance] Don't load REST API when hydrating blocks requests. diff --git a/plugins/woocommerce/changelog/tools-add-e2e-tests-in-ci-jobs b/plugins/woocommerce/changelog/tools-add-e2e-tests-in-ci-jobs deleted file mode 100644 index f5e371f1f54d1..0000000000000 --- a/plugins/woocommerce/changelog/tools-add-e2e-tests-in-ci-jobs +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -CI: adds e2e tests into ci-jobs and ci.yml diff --git a/plugins/woocommerce/changelog/try-checkout-e2e-fix-opr b/plugins/woocommerce/changelog/try-checkout-e2e-fix-opr deleted file mode 100644 index 2e0375fbac85d..0000000000000 --- a/plugins/woocommerce/changelog/try-checkout-e2e-fix-opr +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: This is an E2E test update - no need to call out in the changelog. - - diff --git a/plugins/woocommerce/changelog/try-remove-preserve-global-state-annotations-api-nav-reports b/plugins/woocommerce/changelog/try-remove-preserve-global-state-annotations-api-nav-reports deleted file mode 100644 index c583577656922..0000000000000 --- a/plugins/woocommerce/changelog/try-remove-preserve-global-state-annotations-api-nav-reports +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Remove a few unnecessary test annotations. diff --git a/plugins/woocommerce/changelog/update-analytics-rename-sort-filter-options b/plugins/woocommerce/changelog/update-analytics-rename-sort-filter-options new file mode 100644 index 0000000000000..6e6a34a0a9dfd --- /dev/null +++ b/plugins/woocommerce/changelog/update-analytics-rename-sort-filter-options @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Rename and sort filter options in "Add a filter" in Analytics. diff --git a/plugins/woocommerce/changelog/update-block-templates-div-main b/plugins/woocommerce/changelog/update-block-templates-div-main new file mode 100644 index 0000000000000..46ef4a40d61d4 --- /dev/null +++ b/plugins/woocommerce/changelog/update-block-templates-div-main @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Replace div element with main in block templates diff --git a/plugins/woocommerce/changelog/update-class-property-since-tbd b/plugins/woocommerce/changelog/update-class-property-since-tbd new file mode 100644 index 0000000000000..312133dd9913f --- /dev/null +++ b/plugins/woocommerce/changelog/update-class-property-since-tbd @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Update the version number of some methods in AbstractTemplateCompatibility diff --git a/plugins/woocommerce/changelog/update-local-pickup-title b/plugins/woocommerce/changelog/update-local-pickup-title deleted file mode 100644 index efd1faf9ada0c..0000000000000 --- a/plugins/woocommerce/changelog/update-local-pickup-title +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Sync local pickup title between Checkout page and shipping settings UI diff --git a/plugins/woocommerce/changelog/update-log-file-format-source b/plugins/woocommerce/changelog/update-log-file-format-source new file mode 100644 index 0000000000000..8c4693f44f442 --- /dev/null +++ b/plugins/woocommerce/changelog/update-log-file-format-source @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Ensure the woocommerce_format_log_entry filter hook still has access to the log source value diff --git a/plugins/woocommerce/changelog/update-product-attribute-types b/plugins/woocommerce/changelog/update-product-attribute-types new file mode 100644 index 0000000000000..e508389ecc655 --- /dev/null +++ b/plugins/woocommerce/changelog/update-product-attribute-types @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Woocommerce: update code to data TS changes diff --git a/plugins/woocommerce/changelog/update-product-editor-register-metadata-block-attribute b/plugins/woocommerce/changelog/update-product-editor-register-metadata-block-attribute deleted file mode 100644 index 1059302a4b8f0..0000000000000 --- a/plugins/woocommerce/changelog/update-product-editor-register-metadata-block-attribute +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -[Product Block Editor]: register `metadata` attribute for all blocks diff --git a/plugins/woocommerce/changelog/update-product-task-text b/plugins/woocommerce/changelog/update-product-task-text new file mode 100644 index 0000000000000..0dbbcd3a5ebe3 --- /dev/null +++ b/plugins/woocommerce/changelog/update-product-task-text @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Display `Import Product` task item text and header copies when merchant indicates they are already selling diff --git a/plugins/woocommerce/changelog/update-remove-powered-by-woo-footer b/plugins/woocommerce/changelog/update-remove-powered-by-woo-footer new file mode 100644 index 0000000000000..6077259af0afe --- /dev/null +++ b/plugins/woocommerce/changelog/update-remove-powered-by-woo-footer @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Remove "Powered by WooCommerce" footer diff --git a/plugins/woocommerce/changelog/update-rename-generic-product-collection-location-to-site b/plugins/woocommerce/changelog/update-rename-generic-product-collection-location-to-site deleted file mode 100644 index 42d636232a7b1..0000000000000 --- a/plugins/woocommerce/changelog/update-rename-generic-product-collection-location-to-site +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Product Collection: Rename "other" location from `generic` to `site` diff --git a/plugins/woocommerce/changelog/update-revert-lys-feature-flag-off b/plugins/woocommerce/changelog/update-revert-lys-feature-flag-off new file mode 100644 index 0000000000000..8ff4a297a5ab4 --- /dev/null +++ b/plugins/woocommerce/changelog/update-revert-lys-feature-flag-off @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Toggle LYS feature flag on for post-8.9 diff --git a/plugins/woocommerce/changelog/update-task-list-progress-title b/plugins/woocommerce/changelog/update-task-list-progress-title deleted file mode 100644 index 017d2ded71442..0000000000000 --- a/plugins/woocommerce/changelog/update-task-list-progress-title +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak - -Use "You’re" in task list progress title diff --git a/plugins/woocommerce/changelog/update-templates-documentation b/plugins/woocommerce/changelog/update-templates-documentation new file mode 100644 index 0000000000000..965f0ab28e14d --- /dev/null +++ b/plugins/woocommerce/changelog/update-templates-documentation @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: Update block templates documentation + + diff --git a/plugins/woocommerce/changelog/update-woo-com-to-woocommerce-com b/plugins/woocommerce/changelog/update-woo-com-to-woocommerce-com deleted file mode 100644 index 36ca65dd01296..0000000000000 --- a/plugins/woocommerce/changelog/update-woo-com-to-woocommerce-com +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Update Woo.com references to WooCommerce.com. diff --git a/plugins/woocommerce/client/admin/config/core.json b/plugins/woocommerce/client/admin/config/core.json index 4399c0574c5ac..3a2a476a3bc5d 100644 --- a/plugins/woocommerce/client/admin/config/core.json +++ b/plugins/woocommerce/client/admin/config/core.json @@ -25,7 +25,7 @@ "product-grouped": true, "product-linked": true, "product-pre-publish-modal": true, - "product-custom-fields": false, + "product-custom-fields": true, "remote-inbox-notifications": true, "remote-free-extensions": true, "payment-gateway-suggestions": true, @@ -38,6 +38,6 @@ "wc-pay-promotion": true, "wc-pay-welcome-page": true, "async-product-editor-category-field": false, - "launch-your-store": false + "launch-your-store": true } } diff --git a/plugins/woocommerce/client/legacy/.eslintrc.js b/plugins/woocommerce/client/legacy/.eslintrc.js index a895d4dfc861c..d4560a76ca53e 100644 --- a/plugins/woocommerce/client/legacy/.eslintrc.js +++ b/plugins/woocommerce/client/legacy/.eslintrc.js @@ -17,7 +17,6 @@ module.exports = { 'max-len': [ 2, { 'code': 140 } ], 'no-console': 1 }, - parser: 'babel-eslint', parserOptions: { ecmaVersion: 8, ecmaFeatures: { diff --git a/plugins/woocommerce/client/legacy/css/admin.scss b/plugins/woocommerce/client/legacy/css/admin.scss index 759190dc85c4d..f0a22d1255052 100644 --- a/plugins/woocommerce/client/legacy/css/admin.scss +++ b/plugins/woocommerce/client/legacy/css/admin.scss @@ -7061,10 +7061,10 @@ img.ui-datepicker-trigger { &:hover { box-shadow: inset 0 -1px 0 0 #dfdfdf, - inset 300px 0 0 rgba(156, 93, 144, 0.1); - border-right: 5px solid #9c5d90 !important; + inset 300px 0 0 #f7edf7; + border-right: 5px solid var(--wc-primary) !important; padding-left: 1.5em; - color: #9c5d90; + color: var(--wc-primary); } } } diff --git a/plugins/woocommerce/client/legacy/css/dashboard.scss b/plugins/woocommerce/client/legacy/css/dashboard.scss index 5325e19d99e48..2998d6b13c874 100644 --- a/plugins/woocommerce/client/legacy/css/dashboard.scss +++ b/plugins/woocommerce/client/legacy/css/dashboard.scss @@ -246,7 +246,7 @@ ul.woocommerce_stats { left: 0; letter-spacing: 0.1em; letter-spacing: 0\9; // IE8 & below hack ;-( - color: #9c5d90; + color: var(--wc-primary); } } } diff --git a/plugins/woocommerce/client/legacy/css/woocommerce.scss b/plugins/woocommerce/client/legacy/css/woocommerce.scss index ca5bcc5137541..082932acad5ab 100644 --- a/plugins/woocommerce/client/legacy/css/woocommerce.scss +++ b/plugins/woocommerce/client/legacy/css/woocommerce.scss @@ -2457,4 +2457,13 @@ body:not(.search-results) .twentysixteen .entry-summary { color: #3858E9; text-decoration: none; } + + a.coming-soon-footer-banner-dismiss { + background-image: url('data:image/svg+xml,'); + width: 24px; + height: 24px; + cursor: pointer; + position: absolute; + right: 20px; + } } diff --git a/plugins/woocommerce/client/legacy/js/admin/system-status.js b/plugins/woocommerce/client/legacy/js/admin/system-status.js index 61634f7a38e57..9b33dc30b2880 100644 --- a/plugins/woocommerce/client/legacy/js/admin/system-status.js +++ b/plugins/woocommerce/client/legacy/js/admin/system-status.js @@ -13,8 +13,9 @@ jQuery( function ( $ ) { ) .on( 'click', 'a.debug-report', this.generateReport ) .on( 'click', '#copy-for-support', this.copyReport ) - .on( 'aftercopy', '#copy-for-support', this.copySuccess ) - .on( 'aftercopyfailure', '#copy-for-support', this.copyFail ) + .on( 'click', '#copy-for-github', this.copyGithubReport ) + .on( 'aftercopy', '#copy-for-support, #copy-for-github', this.copySuccess ) + .on( 'aftercopyfailure', '#copy-for-support, #copy-for-github', this.copyFail ) .on( 'click', '#download-for-support', this.downloadReport ); }, @@ -94,11 +95,24 @@ jQuery( function ( $ ) { evt.preventDefault(); }, + /** + * Copy for GitHub report. + * + * @param {Object} event Copy event. + */ + copyGithubReport: function( event ) { + wcClearClipboard(); + var reportValue = $( '#debug-report' ).find( 'textarea' ).val(); + var reportForGithub = '
System Status Report\n\n``' + reportValue + '``\n
'; + wcSetClipboard( reportForGithub, $( this ) ); + event.preventDefault(); + }, + /** * Display a "Copied!" tip when success copying */ - copySuccess: function() { - $( '#copy-for-support' ).tipTip({ + copySuccess: function( event ) { + $( event.target ).tipTip({ 'attribute': 'data-tip', 'activation': 'focus', 'fadeIn': 50, diff --git a/plugins/woocommerce/client/legacy/js/frontend/order-attribution.js b/plugins/woocommerce/client/legacy/js/frontend/order-attribution.js index 28c234e073420..79411e928e1dd 100644 --- a/plugins/woocommerce/client/legacy/js/frontend/order-attribution.js +++ b/plugins/woocommerce/client/legacy/js/frontend/order-attribution.js @@ -65,6 +65,8 @@ if ( ! allow ) { // Reset cookies, and clear form data. removeTrackingCookies(); + } else if ( typeof sbjs === 'undefined' ) { + return; // Do nothing, as sourcebuster.js is not loaded. } else { // If not done yet, initialize sourcebuster.js which populates `sbjs.get` object. sbjs.init( { diff --git a/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js b/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js index 734d73cced267..c3190ac687b0b 100644 --- a/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js +++ b/plugins/woocommerce/client/legacy/js/frontend/woocommerce.js @@ -99,4 +99,24 @@ jQuery( function( $ ) { } } ); + + + $( 'a.coming-soon-footer-banner-dismiss' ).on( 'click', function( e ) { + var target = $( e.target ); + $.ajax( { + type: 'post', + url: target.data( 'rest-url' ), + data: { + meta: { + 'woocommerce_coming_soon_banner_dismissed': 'yes' + } + }, + beforeSend: function ( xhr ) { + xhr.setRequestHeader( 'X-WP-Nonce', target.data( 'rest-nonce' ) ); + }, + complete: function () { + $('#coming-soon-footer-banner').hide(); + } + } ); + } ); }); diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json index aad399f8eb909..35b25a58b2c78 100644 --- a/plugins/woocommerce/composer.json +++ b/plugins/woocommerce/composer.json @@ -2,7 +2,7 @@ "name": "woocommerce/woocommerce", "description": "An eCommerce toolkit that helps you sell anything. Beautifully.", "homepage": "https://woocommerce.com/", - "version": "8.9.0", + "version": "9.0.0", "type": "wordpress-plugin", "license": "GPL-3.0-or-later", "prefer-stable": true, @@ -113,7 +113,7 @@ "phpcbf -p" ], "makepot-audit": [ - "wp --allow-root i18n make-pot . --exclude=\".github,.wordpress-org,bin,sample-data,node_modules,tests\" --slug=woocommerce" + "wp --allow-root i18n make-pot . --include=\"woocommerce.php,client,i18n,includes,lib,packages,patterns,src,templates\" --slug=woocommerce" ], "makepot": [ "@makepot-audit --skip-audit" diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-data.php b/plugins/woocommerce/includes/abstracts/abstract-wc-data.php index 0b3d9a75e90c7..c9761db92559c 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-data.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-data.php @@ -815,7 +815,7 @@ public function set_props( $props, $context = 'set' ) { * Sets a prop for a setter method. * * This stores changes in a special array so we can track what needs saving - * the the DB later. + * the DB later. * * @since 3.0.0 * @param string $prop Name of prop to set. diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php b/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php index ca887b87e2366..756f4cdc8fceb 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php @@ -38,19 +38,17 @@ class WC_Admin_Marketplace_Promotions { * @return void */ public static function init() { - register_deactivation_hook( WC_PLUGIN_FILE, array( __CLASS__, 'clear_scheduled_event' ) ); - /** * Filter to suppress the requests for and showing of marketplace promotions. * * @since 8.8 */ if ( apply_filters( 'woocommerce_marketplace_suppress_promotions', false ) ) { - add_action( 'init', array( __CLASS__, 'clear_scheduled_event' ), 13 ); - return; } + register_deactivation_hook( WC_PLUGIN_FILE, array( __CLASS__, 'clear_scheduled_event' ) ); + // Add the callback for our scheduled action. if ( ! has_action( self::SCHEDULED_ACTION_HOOK, array( __CLASS__, 'fetch_marketplace_promotions' ) ) ) { add_action( self::SCHEDULED_ACTION_HOOK, array( __CLASS__, 'fetch_marketplace_promotions' ) ); @@ -77,7 +75,11 @@ public static function init() { */ public static function schedule_promotion_fetch() { // Schedule the action twice a day using Action Scheduler. - if ( false === as_has_scheduled_action( self::SCHEDULED_ACTION_HOOK ) ) { + if ( + function_exists( 'as_has_scheduled_action' ) + && function_exists( 'as_schedule_recurring_action' ) + && false === as_has_scheduled_action( self::SCHEDULED_ACTION_HOOK ) + ) { as_schedule_recurring_action( time(), self::SCHEDULED_ACTION_INTERVAL, self::SCHEDULED_ACTION_HOOK ); } } @@ -295,7 +297,9 @@ private static function append_bubble( string $menu_item_text, string $bubble_te * @return void */ public static function clear_scheduled_event() { - as_unschedule_all_actions( self::SCHEDULED_ACTION_HOOK ); + if ( function_exists( 'as_unschedule_all_actions' ) ) { + as_unschedule_all_actions( self::SCHEDULED_ACTION_HOOK ); + } } } diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-notices.php b/plugins/woocommerce/includes/admin/class-wc-admin-notices.php index 3a4f7ad7fa5cd..01163bbe5f33b 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-notices.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-notices.php @@ -10,6 +10,7 @@ use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; use Automattic\WooCommerce\Internal\Utilities\Users; use Automattic\WooCommerce\Internal\Utilities\WebhookUtil; +use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; defined( 'ABSPATH' ) || exit; @@ -217,7 +218,7 @@ private static function maybe_add_legacy_api_removal_notice() { * or if the Legacy REST API extension is installed, and remove the notice about Legacy webhooks * if no such webhooks exist anymore or if the Legacy REST API extension is installed. * - * TODO: Change this method in WooCommerce 9.0 so that the notice gets removed if the Legacy REST API extension is installed and active. + * TODO: Change this method in WooCommerce 9.0 so that the notice get removed if the Legacy REST API extension is installed and active. */ private static function maybe_remove_legacy_api_removal_notice() { $plugin_is_active = is_plugin_active( 'woocommerce-legacy-rest-api/woocommerce-legacy-rest-api.php' ); @@ -229,6 +230,11 @@ private static function maybe_remove_legacy_api_removal_notice() { if ( self::has_notice( 'legacy_webhooks_unsupported_in_woo_90' ) && ( $plugin_is_active || 0 === wc_get_container()->get( WebhookUtil::class )->get_legacy_webhooks_count() ) ) { self::remove_notice( 'legacy_webhooks_unsupported_in_woo_90' ); } + + if ( self::has_notice( 'legacy_rest_api_is_incompatible_with_hpos' ) && + ! ( 'yes' === get_option( 'woocommerce_api_enabled' ) && 'yes' === get_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION ) ) ) { + self::remove_notice( 'legacy_rest_api_is_incompatible_with_hpos' ); + } } // phpcs:enable Generic.Commenting.Todo.TaskFound diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-settings.php b/plugins/woocommerce/includes/admin/class-wc-admin-settings.php index bc3ce2468ce6e..32df361f14e86 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-settings.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-settings.php @@ -57,7 +57,9 @@ public static function get_settings_pages() { $settings[] = include __DIR__ . '/settings/class-wc-settings-accounts.php'; $settings[] = include __DIR__ . '/settings/class-wc-settings-emails.php'; $settings[] = include __DIR__ . '/settings/class-wc-settings-integrations.php'; - $settings[] = include __DIR__ . '/settings/class-wc-settings-site-visibility.php'; + if ( \Automattic\WooCommerce\Admin\Features\Features::is_enabled( 'launch-your-store' ) ) { + $settings[] = include __DIR__ . '/settings/class-wc-settings-site-visibility.php'; + } $settings[] = include __DIR__ . '/settings/class-wc-settings-advanced.php'; self::$settings = apply_filters( 'woocommerce_get_settings_pages', $settings ); diff --git a/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php b/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php index 4c43f0ffc57b9..1f8928c5eda62 100644 --- a/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php +++ b/plugins/woocommerce/includes/admin/helper/class-wc-helper-admin.php @@ -50,6 +50,8 @@ function ( $product ) { $installed_products ); + $woo_connect_notice_type = WC_Helper_Updater::get_woo_connect_notice_type(); + $settings['wccomHelper'] = array( 'isConnected' => WC_Helper::is_site_connected(), 'connectURL' => self::get_connection_url(), @@ -63,6 +65,7 @@ function ( $product ) { 'wooUpdateManagerInstallUrl' => WC_Woo_Update_Manager_Plugin::generate_install_url(), 'wooUpdateManagerPluginSlug' => WC_Woo_Update_Manager_Plugin::WOO_UPDATE_MANAGER_SLUG, 'wooUpdateCount' => WC_Helper_Updater::get_updates_count_based_on_site_status(), + 'woocomConnectNoticeType' => $woo_connect_notice_type, ); return $settings; diff --git a/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php b/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php index 9f1c0d7008469..4cd4310a3e818 100644 --- a/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php +++ b/plugins/woocommerce/includes/admin/helper/class-wc-helper-updater.php @@ -33,7 +33,7 @@ public static function load() { * Add the hook for modifying default WPCore update notices on the plugins management page. */ public static function add_hook_for_modifying_update_notices() { - if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_active() ) { + if ( ! WC_Woo_Update_Manager_Plugin::is_plugin_active() || ! WC_Helper::is_site_connected() ) { add_action( 'load-plugins.php', array( __CLASS__, 'setup_update_plugins_messages' ), 11 ); } } @@ -155,12 +155,46 @@ public static function transient_update_themes( $transient ) { * @return void. */ public static function setup_update_plugins_messages() { + $is_site_connected = WC_Helper::is_site_connected(); foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) { $filename = $plugin['_filename']; - add_action( 'in_plugin_update_message-' . $filename, array( __CLASS__, 'add_install_marketplace_plugin_message' ), 10, 2 ); + if ( $is_site_connected ) { + add_action( 'in_plugin_update_message-' . $filename, array( __CLASS__, 'add_install_marketplace_plugin_message' ), 10, 2 ); + } else { + add_action( 'in_plugin_update_message-' . $filename, array( __CLASS__, 'add_connect_woocom_plugin_message' ) ); + } } } + /** + * Runs on in_plugin_update_message-{file-name}, show a message to connect to woocommerce.com for unconnected stores + * + * @return void. + */ + public static function add_connect_woocom_plugin_message() { + $connect_page_url = add_query_arg( + array( + 'page' => 'wc-admin', + 'tab' => 'my-subscriptions', + 'path' => rawurlencode( '/extensions' ), + ), + admin_url( 'admin.php' ) + ); + + printf( + wp_kses( + /* translators: 1: Woo Update Manager plugin install URL */ + __( ' Connect your store to woocommerce.com to update.', 'woocommerce' ), + array( + 'a' => array( + 'href' => array(), + ), + ) + ), + esc_url( $connect_page_url ), + ); + } + /** * Runs on in_plugin_update_message-{file-name}, show a message to install the Woo Marketplace plugin, on plugin update notification, * if the Woo Marketplace plugin isn't already installed. @@ -422,13 +456,22 @@ private static function _update_check( $payload ) { 'errors' => array(), ); - $request = WC_Helper_API::post( - 'update-check', - array( - 'body' => wp_json_encode( array( 'products' => $payload ) ), - 'authenticated' => true, - ) - ); + if ( WC_Helper::is_site_connected() ) { + $request = WC_Helper_API::post( + 'update-check', + array( + 'body' => wp_json_encode( array( 'products' => $payload ) ), + 'authenticated' => true, + ) + ); + } else { + $request = WC_Helper_API::post( + 'update-check-public', + array( + 'body' => wp_json_encode( array( 'products' => $payload ) ), + ) + ); + } if ( wp_remote_retrieve_response_code( $request ) !== 200 ) { $data['errors'][] = 'http-error'; @@ -503,7 +546,7 @@ public static function get_updates_count() { */ public static function get_updates_count_based_on_site_status() { if ( ! WC_Helper::is_site_connected() ) { - return 1; + return 0; } $count = self::get_updates_count() ?? 0; @@ -514,6 +557,45 @@ public static function get_updates_count_based_on_site_status() { return $count; } + /** + * Get the type of woo connect notice to be shown in the WC Settings and Marketplace pages. + * - If a store is connected to woocommerce.com or has no installed woo plugins, return 'none'. + * - If a store has installed woo plugins but no updates, return 'short'. + * - If a store has an installed woo plugin with update, return 'long'. + * + * @return string The notice type, 'none', 'short', or 'long'. + */ + public static function get_woo_connect_notice_type() { + if ( WC_Helper::is_site_connected() ) { + return 'none'; + } + + $woo_plugins = WC_Helper::get_local_woo_plugins(); + + if ( empty( $woo_plugins ) ) { + return 'none'; + } + + $update_data = self::get_update_data(); + + if ( empty( $update_data ) ) { + return 'short'; + } + + // Scan local plugins. + foreach ( $woo_plugins as $plugin ) { + if ( empty( $update_data[ $plugin['_product_id'] ] ) ) { + continue; + } + + if ( version_compare( $plugin['Version'], $update_data[ $plugin['_product_id'] ]['version'], '<' ) ) { + return 'long'; + } + } + + return 'short'; + } + /** * Return the updates count markup. * diff --git a/plugins/woocommerce/includes/admin/settings/class-wc-settings-advanced.php b/plugins/woocommerce/includes/admin/settings/class-wc-settings-advanced.php index 16985529930a5..6df71562bf8d0 100644 --- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-advanced.php +++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-advanced.php @@ -135,25 +135,6 @@ protected function get_settings_for_default_section() { 'autoload' => false, ), - Features::is_enabled( 'launch-your-store' ) ? array( - 'title' => __( 'Coming soon page', 'woocommerce' ), - 'desc' => __( 'TBD', 'woocommerce' ), - 'id' => 'woocommerce_coming_soon_page_id', - 'type' => 'single_select_page_with_search', - 'default' => '', - 'class' => 'wc-page-search', - 'css' => 'min-width:300px;', - 'args' => array( - 'exclude' => - array( - wc_get_page_id( 'checkout' ), - wc_get_page_id( 'myaccount' ), - ), - ), - 'desc_tip' => true, - 'autoload' => false, - ) : array(), - array( 'type' => 'sectionend', 'id' => 'advanced_page_options', diff --git a/plugins/woocommerce/includes/admin/views/html-admin-page-status-report.php b/plugins/woocommerce/includes/admin/views/html-admin-page-status-report.php index f580e3691944d..e987509491d14 100644 --- a/plugins/woocommerce/includes/admin/views/html-admin-page-status-report.php +++ b/plugins/woocommerce/includes/admin/views/html-admin-page-status-report.php @@ -29,6 +29,20 @@ $active_plugins_count = is_countable( $active_plugins ) ? count( $active_plugins ) : 0; $inactive_plugins_count = is_countable( $inactive_plugins ) ? count( $inactive_plugins ) : 0; +// Include necessary WordPress file to use get_plugin_data() +if ( ! function_exists( 'get_plugin_data' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; +} +// Define the path to the main WooCommerce plugin file using the correct constant +$plugin_path = WP_PLUGIN_DIR . '/woocommerce/woocommerce.php'; +// Initialize the WooCommerce version variable +$wc_version = ''; +// Check if the plugin file exists before trying to access it +if (file_exists($plugin_path)) { + $plugin_data = get_plugin_data($plugin_path); + $wc_version = $plugin_data["Version"] ?? ''; // Use null coalescing operator to handle undefined index +} + ?>

@@ -49,6 +63,9 @@ +

'; foreach ( $supported_gateways as $pay_button_id ) { - echo sprintf( '
', esc_attr( $pay_button_id ) ); + printf( '
', esc_attr( $pay_button_id ) ); } echo '
'; } diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php index b9bd901ea79ae..fe8608fc25dfe 100644 --- a/plugins/woocommerce/includes/wc-update-functions.php +++ b/plugins/woocommerce/includes/wc-update-functions.php @@ -18,8 +18,11 @@ defined( 'ABSPATH' ) || exit; +use Automattic\WooCommerce\Admin\Notes\Note; +use Automattic\WooCommerce\Admin\Notes\Notes; use Automattic\WooCommerce\Database\Migrations\MigrationHelper; use Automattic\WooCommerce\Internal\Admin\Marketing\MarketingSpecs; +use Automattic\WooCommerce\Internal\Admin\Notes\WooSubscriptionsNotes; use Automattic\WooCommerce\Internal\AssignDefaultCategory; use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer; use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; @@ -2668,8 +2671,20 @@ function wc_update_870_prevent_listing_of_transient_files_directory() { } /** - * Add woocommerce_show_lys_tour. + * If it exists, remove and recreate the inbox note that asks users to connect to `Woo.com` so that the domain name is changed to the updated `WooCommerce.com`. */ -function wc_update_890_add_launch_your_store_tour_option() { - update_option( 'woocommerce_show_lys_tour', 'yes' ); +function wc_update_890_update_connect_to_woocommerce_note() { + $note = Notes::get_note_by_name( WooSubscriptionsNotes::CONNECTION_NOTE_NAME ); + if ( ! is_a( $note, 'Automattic\WooCommerce\Admin\Notes\Note' ) ) { + return; + } + if ( ! str_contains( $note->get_title(), 'Woo.com' ) ) { + return; + } + if ( $note->get_status() !== Note::E_WC_ADMIN_NOTE_SNOOZED && $note->get_status() !== Note::E_WC_ADMIN_NOTE_UNACTIONED ) { + return; + } + Notes::delete_notes_with_name( WooSubscriptionsNotes::CONNECTION_NOTE_NAME ); + $new_note = WooSubscriptionsNotes::get_note(); + $new_note->save(); } diff --git a/plugins/woocommerce/includes/wc-webhook-functions.php b/plugins/woocommerce/includes/wc-webhook-functions.php index 25dc750910747..ba4a033a7d241 100644 --- a/plugins/woocommerce/includes/wc-webhook-functions.php +++ b/plugins/woocommerce/includes/wc-webhook-functions.php @@ -159,6 +159,11 @@ function wc_get_webhook_statuses() { * @return bool */ function wc_load_webhooks( $status = '', $limit = null ) { + // short-circuit if webhooks should not be loaded at all. + if ( ! is_null( $limit ) && $limit <= 0 ) { + return false; + } + $data_store = WC_Data_Store::load( 'webhook' ); $webhooks = $data_store->get_webhooks_ids( $status ); $loaded = 0; diff --git a/plugins/woocommerce/includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-activate-product.php b/plugins/woocommerce/includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-activate-product.php index 1b7731b439adb..a14adf25a50f0 100644 --- a/plugins/woocommerce/includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-activate-product.php +++ b/plugins/woocommerce/includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-activate-product.php @@ -79,6 +79,11 @@ private function activate_plugin( $product_id ) { throw new Installer_Error( Installer_Error_Codes::UNKNOWN_FILENAME ); } + // If the plugin is already active, make sure we call the registration hook. + if ( is_plugin_active( $filename ) ) { + WC_Helper::activated_plugin( $filename ); + } + $result = activate_plugin( $filename ); if ( is_wp_error( $result ) ) { diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index a09cebde84637..26e25da64e249 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/plugin-woocommerce", "private": true, "title": "WooCommerce", - "version": "8.9.0", + "version": "9.0.0", "homepage": "https://woocommerce.com/", "repository": { "type": "git", @@ -28,15 +28,19 @@ "env:restart": "pnpm wp-env destroy && pnpm wp-env start --update", "env:start": "pnpm wp-env start", "env:stop": "pnpm wp-env stop", - "env:test": "WP_ENV_LIFECYCLE_SCRIPT_AFTER_START='./tests/e2e-pw/bin/test-env-setup.sh' && pnpm env:dev && pnpm playwright install chromium", - "env:test:cot": "WP_ENV_LIFECYCLE_SCRIPT_AFTER_START='ENABLE_HPOS=1 ./tests/e2e-pw/bin/test-env-setup.sh' && ENABLE_HPOS=1 pnpm env:dev", + "env:test": "pnpm env:dev && pnpm playwright install chromium", + "env:test:no-hpos": "DISABLE_HPOS=1 pnpm env:test", "env:perf:install-k6": "curl https://github.com/grafana/k6/releases/download/v0.33.0/k6-v0.33.0-linux-amd64.tar.gz -L | tar xvz --strip-components 1", "env:perf": "pnpm env:dev && pnpm env:performance-init && pnpm env:perf:install-k6", "preinstall": "npx only-allow pnpm", "postinstall": "composer install", "lint": "pnpm --if-present '/^lint:lang:.*$/'", + "lint:changes:branch": "pnpm '/^lint:changes:branch:.*$/'", "lint:fix": "pnpm --if-present '/^lint:fix:lang:.*$/'", "lint:fix:lang:php": "composer run-script phpcbf", + "lint:changes:branch:js": "bash ./bin/eslint-branch.sh", + "lint:changes:branch:php": "pnpm lint:php:changes:branch", + "lint:lang:js": "eslint . --ext=js,ts", "lint:lang:php": "composer run-script phpcs", "lint:php": "composer run-script phpcs", "lint:php:changes": "composer run-script lint", @@ -55,6 +59,7 @@ "test:php": "./vendor/bin/phpunit -c ./phpunit.xml", "test:php:watch": "./vendor/bin/phpunit-watcher watch", "test:metrics": "USE_WP_ENV=1 pnpm playwright test --config=tests/metrics/playwright.config.js", + "test:metrics:ci": "../../.github/workflows/scripts/run-metrics.sh", "test:php:env": "wp-env run --env-cwd='wp-content/plugins/woocommerce' tests-cli vendor/bin/phpunit -c phpunit.xml --verbose", "test:php:env:watch": "wp-env run --env-cwd='wp-content/plugins/woocommerce' tests-cli vendor/bin/phpunit-watcher watch --verbose", "test:unit": "pnpm test:php", @@ -80,7 +85,7 @@ "build_step": "pnpm build:zip", "ci": { "lint": { - "command": "lint:php:changes:branch ", + "command": "lint:changes:branch ", "changes": [ "composer.lock", "includes/**/*.php", @@ -89,7 +94,8 @@ "templates/**/*.php", "tests/php/**/*.php", "tests/legacy/unit-tests/**/*.php", - "tests/unit-tests/**/*.php" + "tests/unit-tests/**/*.php", + "tests/**/*.js" ] }, "tests": [ @@ -201,6 +207,34 @@ "start": "env:test" } }, + { + "name": "Core e2e tests - HPOS disabled", + "testType": "e2e", + "command": "test:e2e-pw", + "shardingArguments": [ + "--shard=1/5", + "--shard=2/5", + "--shard=3/5", + "--shard=4/5", + "--shard=5/5" + ], + "events": [ + "push" + ], + "changes": [ + "client/admin/config/*.json", + "composer.lock", + "includes/**/*.php", + "patterns/**/*.php", + "src/**/*.php", + "templates/**/*.php", + "tests/php/**/*.php", + "tests/e2e-pw/**" + ], + "testEnv": { + "start": "env:test:no-hpos" + } + }, { "name": "Core API tests", "testType": "api", @@ -221,6 +255,27 @@ "start": "env:test" } }, + { + "name": "Core API tests - HPOS disabled", + "testType": "api", + "command": "test:api-pw", + "changes": [ + "client/admin/config/*.json", + "composer.lock", + "includes/**/*.php", + "patterns/**/*.php", + "src/**/*.php", + "templates/**/*.php", + "tests/php/**/*.php", + "tests/api-core-tests/**" + ], + "events": [ + "push" + ], + "testEnv": { + "start": "env:test:no-hpos" + } + }, { "name": "Core Performance tests (K6)", "testType": "performance", @@ -239,6 +294,21 @@ "testEnv": { "start": "env:perf" } + }, + { + "name": "Metrics", + "testType": "performance", + "command": "test:metrics:ci", + "changes": [ + "client/admin/config/*.json", + "composer.lock", + "includes/**/*.php", + "patterns/**/*.php", + "src/**/*.php", + "templates/**/*.php", + "templates/**/*.html", + "tests/metrics/**" + ] } ] } @@ -286,14 +356,14 @@ "prettier": "npm:wp-prettier@^2.8.5", "stylelint": "^14.16.1", "typescript": "^5.3.3", - "uuid": "^8.3.2", + "uuid": "^9.0.1", "webpack": "5.70.0", "webpack-cli": "3.3.12", "wireit": "0.14.3", "wp-textdomain": "1.0.1" }, "engines": { - "node": "^16.14.1", + "node": "^20.11.1", "pnpm": "^8.12.1" }, "browserslist": [ diff --git a/plugins/woocommerce/patterns/coming-soon-entire-site.php b/plugins/woocommerce/patterns/coming-soon-entire-site.php new file mode 100644 index 0000000000000..2f33bbb92d000 --- /dev/null +++ b/plugins/woocommerce/patterns/coming-soon-entire-site.php @@ -0,0 +1,195 @@ + + + +
+
+
+
+
+
+ + +
+
+ + + + +
+
+ + + +
+

Pardon our dust! We're working on something amazing -- check back soon!

+
+ + + +
+
+ +
+
+
+
+
+ diff --git a/plugins/woocommerce/patterns/coming-soon-store-only.php b/plugins/woocommerce/patterns/coming-soon-store-only.php new file mode 100644 index 0000000000000..c98d786063f9a --- /dev/null +++ b/plugins/woocommerce/patterns/coming-soon-store-only.php @@ -0,0 +1,51 @@ + + + +
+ +'; +} +?> + + +
+ + + + +

+ + + + + + + +

+ + + + +
+ + +'; +} +?> + +
+ diff --git a/plugins/woocommerce/patterns/coming-soon.php b/plugins/woocommerce/patterns/coming-soon.php new file mode 100644 index 0000000000000..e3a15aceb6746 --- /dev/null +++ b/plugins/woocommerce/patterns/coming-soon.php @@ -0,0 +1,14 @@ + + + diff --git a/plugins/woocommerce/readme.txt b/plugins/woocommerce/readme.txt index d6eb0f917bc62..9dd54c2fcc5aa 100644 --- a/plugins/woocommerce/readme.txt +++ b/plugins/woocommerce/readme.txt @@ -4,7 +4,7 @@ Tags: online store, ecommerce, shop, shopping cart, sell online, storefront, che Requires at least: 6.4 Tested up to: 6.5 Requires PHP: 7.4 -Stable tag: 8.7.0 +Stable tag: 8.8.3 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -57,9 +57,9 @@ Unlike hosted ecommerce solutions, WooCommerce store data is future-proof; you Developers can use [WooCommerce](https://woocommerce.com/woocommerce/) to create, customize, and scale a store to meet a client’s exact specifications, making enhancements through extensions or custom solutions. - Leverage [hooks and filters](https://woocommerce.com/document/introduction-to-hooks-actions-and-filters/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) to modify or create functionality. -- Integrate virtually any service using a robust [REST API](https://woocommerce.com/document/woocommerce-rest-api/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) and webhooks. +- Integrate virtually any service using a robust [REST API](https://developer.woocommerce.com/docs/getting-started-with-the-woocommerce-rest-api/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) and webhooks. - Design and build custom content blocks with React. -- [Inspect and modify](https://woocommerce.com/documentation/plugins/woocommerce/woocommerce-codex/extending/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) any aspect of the core plugin code. +- [Inspect and modify](https://developer.woocommerce.com/docs/category/extension-development/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) any aspect of the core plugin code. - Speed up development with a lightning-fast [CLI](https://woocommerce.github.io/code-reference/classes/wc-cli-rest-command.html?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing). The core platform is tested rigorously and often, supported by a dedicated development team working across time zones. Comprehensive documentation is updated with each release, empowering you to build exactly the store required. @@ -84,7 +84,7 @@ WooCommerce is translated into multiple languages, including Danish, Ukrainian, For help setting up and configuring WooCommerce, please refer to [Getting Started](https://woocommerce.com/documentation/plugins/woocommerce/getting-started/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) and the [New WooCommerce Store Owner Guide](https://woocommerce.com/guides/new-store/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing). -For extending or theming WooCommerce, see our [codex](https://woocommerce.com/documentation/plugins/woocommerce/woocommerce-codex/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), as well as the [Plugin Developer Handbook](https://woocommerce.com/document/create-a-plugin/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing). +For extending or theming WooCommerce, see our [documentation](https://github.com/woocommerce/woocommerce/tree/trunk/docs), as well as the [Plugin Developer Best Practices](https://github.com/woocommerce/woocommerce/blob/trunk/docs/extension-development/extension-development-best-practices.md). = Where can I get help or talk to other users about WooCommerce Core? = @@ -165,6 +165,6 @@ WooCommerce comes with some sample data you can use to see how products look; im == Changelog == -= 8.9.0 2024-XX-XX = += 9.0.0 2024-XX-XX = [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt). diff --git a/plugins/woocommerce/sample-data/sample_products.xml b/plugins/woocommerce/sample-data/sample_products.xml index 3b8926fb271a6..b9b07b4a3db18 100644 --- a/plugins/woocommerce/sample-data/sample_products.xml +++ b/plugins/woocommerce/sample-data/sample_products.xml @@ -31,7 +31,7 @@ 1 shopmanager - info@woo.com + info@woocommerce.com diff --git a/plugins/woocommerce/src/Admin/API/Init.php b/plugins/woocommerce/src/Admin/API/Init.php index 4464b44722a3e..5178aae09c8db 100644 --- a/plugins/woocommerce/src/Admin/API/Init.php +++ b/plugins/woocommerce/src/Admin/API/Init.php @@ -51,7 +51,6 @@ public function __construct() { add_filter( 'woocommerce_rest_prepare_shop_order_object', array( __CLASS__, 'add_currency_symbol_to_order_response' ) ); include_once WC_ABSPATH . 'includes/admin/class-wc-admin-upload-downloadable-product.php'; - } /** @@ -105,6 +104,10 @@ public function rest_api_init() { $product_form_controllers[] = 'Automattic\WooCommerce\Admin\API\ProductForm'; } + if ( Features::is_enabled( 'launch-your-store' ) ) { + $controllers[] = 'Automattic\WooCommerce\Admin\API\LaunchYourStore'; + } + if ( Features::is_enabled( 'analytics' ) ) { $analytics_controllers = array( 'Automattic\WooCommerce\Admin\API\Customers', @@ -134,8 +137,7 @@ public function rest_api_init() { // The performance indicators controller must be registered last, after other /stats endpoints have been registered. $analytics_controllers[] = 'Automattic\WooCommerce\Admin\API\Reports\PerformanceIndicators\Controller'; - - $controllers = array_merge( $controllers, $analytics_controllers, $product_form_controllers ); + $controllers = array_merge( $controllers, $analytics_controllers, $product_form_controllers ); } /** diff --git a/plugins/woocommerce/src/Admin/API/LaunchYourStore.php b/plugins/woocommerce/src/Admin/API/LaunchYourStore.php new file mode 100644 index 0000000000000..a99d73b34f8b9 --- /dev/null +++ b/plugins/woocommerce/src/Admin/API/LaunchYourStore.php @@ -0,0 +1,98 @@ +namespace, + '/' . $this->rest_base . '/initialize-coming-soon', + array( + array( + 'methods' => 'POST', + 'callback' => array( $this, 'initialize_coming_soon' ), + 'permission_callback' => array( $this, 'must_be_shop_manager_or_admin' ), + ), + ) + ); + } + + /** + * User must be either shop_manager or administrator. + * + * @return bool + */ + public function must_be_shop_manager_or_admin() { + // phpcs:ignore + if ( ! current_user_can( 'manage_woocommerce' ) && ! current_user_can( 'administrator' ) ) { + return false; + } + return true; + } + + /** + * Initializes options for coming soon. Does not override if options exist. + * + * @return bool|void + */ + public function initialize_coming_soon() { + $current_user_id = get_current_user_id(); + // Abort if we don't have a user id for some reason. + if ( ! $current_user_id ) { + return; + } + + $coming_soon = 'yes'; + $store_pages_only = WCAdminHelper::is_site_fresh() ? 'no' : 'yes'; + $private_link = 'no'; + $share_key = wp_generate_password( 32, false ); + + add_option( 'woocommerce_coming_soon', $coming_soon ); + add_option( 'woocommerce_store_pages_only', $store_pages_only ); + add_option( 'woocommerce_private_link', $private_link ); + add_option( 'woocommerce_share_key', $share_key ); + + wc_admin_record_tracks_event( + 'launch_your_store_initialize_coming_soon', + array( + 'coming_soon' => $coming_soon, + 'store_pages_only' => $store_pages_only, + 'private_link' => $private_link, + ) + ); + + return true; + } +} diff --git a/plugins/woocommerce/src/Admin/API/Notes.php b/plugins/woocommerce/src/Admin/API/Notes.php index a1503406f6656..d96d8165c6cd0 100644 --- a/plugins/woocommerce/src/Admin/API/Notes.php +++ b/plugins/woocommerce/src/Admin/API/Notes.php @@ -385,7 +385,7 @@ public function prepare_note_data_for_response( $note, $request ) { } /** - * Prepare an array with the the requested updates. + * Prepare an array with the requested updates. * * @param WP_REST_Request $request Request object. * @return array A list of the requested updates values. diff --git a/plugins/woocommerce/src/Admin/API/Options.php b/plugins/woocommerce/src/Admin/API/Options.php index 5d27a482a1d97..7adab2c3046e2 100644 --- a/plugins/woocommerce/src/Admin/API/Options.php +++ b/plugins/woocommerce/src/Admin/API/Options.php @@ -225,9 +225,7 @@ public static function get_default_option_permissions() { 'woocommerce_store_pages_only', 'woocommerce_private_link', 'woocommerce_share_key', - 'launch-status', 'woocommerce_show_lys_tour', - 'woocommerce_coming_soon_page_id', // WC Test helper options. 'wc-admin-test-helper-rest-api-filters', 'wc_admin_helper_feature_values', diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php index a3ecb5423094e..81e85182f1cb5 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php @@ -211,24 +211,30 @@ public function get_item_schema() { 'readonly' => true, ), 'extended_info' => array( - 'products' => array( + 'products' => array( 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit' ), 'description' => __( 'List of order product IDs, names, quantities.', 'woocommerce' ), ), - 'coupons' => array( + 'coupons' => array( 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit' ), 'description' => __( 'List of order coupons.', 'woocommerce' ), ), - 'customer' => array( + 'customer' => array( 'type' => 'object', 'readonly' => true, 'context' => array( 'view', 'edit' ), 'description' => __( 'Order customer information.', 'woocommerce' ), ), + 'attribution' => array( + 'type' => 'object', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + 'description' => __( 'Order attribution information.', 'woocommerce' ), + ), ), ), ); @@ -526,6 +532,7 @@ public function get_export_columns() { 'num_items_sold' => __( 'Items sold', 'woocommerce' ), 'coupons' => __( 'Coupon(s)', 'woocommerce' ), 'net_total' => __( 'N. Revenue', 'woocommerce' ), + 'attribution' => __( 'Attribution', 'woocommerce' ), ); /** @@ -558,6 +565,7 @@ public function prepare_item_for_export( $item ) { 'num_items_sold' => $item['num_items_sold'], 'coupons' => isset( $item['extended_info']['coupons'] ) ? $this->get_coupons( $item['extended_info']['coupons'] ) : null, 'net_total' => $item['net_total'], + 'attribution' => $item['extended_info']['attribution']['origin'], ); /** diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php index ca92e9b71650c..007bcf16ea823 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/DataStore.php @@ -7,6 +7,9 @@ defined( 'ABSPATH' ) || exit; +use Automattic\WooCommerce\Internal\Traits\OrderAttributionMeta; +use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; +use Automattic\WooCommerce\Utilities\OrderUtil; use Automattic\WooCommerce\Admin\API\Reports\DataStore as ReportsDataStore; use Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface; use Automattic\WooCommerce\Admin\API\Reports\SqlQuery; @@ -18,6 +21,7 @@ * API\Reports\Orders\DataStore. */ class DataStore extends ReportsDataStore implements DataStoreInterface { + use OrderAttributionMeta; /** * Dynamically sets the date column name based on configuration @@ -338,13 +342,14 @@ protected function normalize_order_by( $order_by ) { * @param array $query_args Query parameters. */ protected function include_extended_info( &$orders_data, $query_args ) { - $mapped_orders = $this->map_array_by_key( $orders_data, 'order_id' ); - $related_orders = $this->get_orders_with_parent_id( $mapped_orders ); - $order_ids = array_merge( array_keys( $mapped_orders ), array_keys( $related_orders ) ); - $products = $this->get_products_by_order_ids( $order_ids ); - $coupons = $this->get_coupons_by_order_ids( array_keys( $mapped_orders ) ); - $customers = $this->get_customers_by_orders( $orders_data ); - $mapped_customers = $this->map_array_by_key( $customers, 'customer_id' ); + $mapped_orders = $this->map_array_by_key( $orders_data, 'order_id' ); + $related_orders = $this->get_orders_with_parent_id( $mapped_orders ); + $order_ids = array_merge( array_keys( $mapped_orders ), array_keys( $related_orders ) ); + $products = $this->get_products_by_order_ids( $order_ids ); + $coupons = $this->get_coupons_by_order_ids( array_keys( $mapped_orders ) ); + $order_attributions = $this->get_order_attributions_by_order_ids( array_keys( $mapped_orders ) ); + $customers = $this->get_customers_by_orders( $orders_data ); + $mapped_customers = $this->map_array_by_key( $customers, 'customer_id' ); $mapped_data = array(); foreach ( $products as $product ) { @@ -384,7 +389,7 @@ protected function include_extended_info( &$orders_data, $query_args ) { foreach ( $coupons as $coupon ) { if ( ! isset( $mapped_data[ $coupon['order_id'] ] ) ) { - $mapped_data[ $product['order_id'] ]['coupons'] = array(); + $mapped_data[ $coupon['order_id'] ]['coupons'] = array(); } $mapped_data[ $coupon['order_id'] ]['coupons'][] = array( @@ -394,15 +399,22 @@ protected function include_extended_info( &$orders_data, $query_args ) { } foreach ( $orders_data as $key => $order_data ) { - $defaults = array( - 'products' => array(), - 'coupons' => array(), - 'customer' => array(), + $defaults = array( + 'products' => array(), + 'coupons' => array(), + 'customer' => array(), + 'attribution' => array(), ); - $orders_data[ $key ]['extended_info'] = isset( $mapped_data[ $order_data['order_id'] ] ) ? array_merge( $defaults, $mapped_data[ $order_data['order_id'] ] ) : $defaults; + $order_id = $order_data['order_id']; + + $orders_data[ $key ]['extended_info'] = isset( $mapped_data[ $order_id ] ) ? array_merge( $defaults, $mapped_data[ $order_id ] ) : $defaults; if ( $order_data['customer_id'] && isset( $mapped_customers[ $order_data['customer_id'] ] ) ) { $orders_data[ $key ]['extended_info']['customer'] = $mapped_customers[ $order_data['customer_id'] ]; } + + $source_type = $order_attributions[ $order_id ]['_wc_order_attribution_source_type'] ?? ''; + $utm_source = $order_attributions[ $order_id ]['_wc_order_attribution_utm_source'] ?? ''; + $orders_data[ $key ]['extended_info']['attribution']['origin'] = $this->get_origin_label( $source_type, $utm_source ); } } @@ -534,6 +546,52 @@ protected function get_coupons_by_order_ids( $order_ids ) { return $coupons; } + /** + * Get order attributions data from order IDs. + * + * @param array $order_ids Array of order IDs. + * @return array + */ + protected function get_order_attributions_by_order_ids( $order_ids ) { + global $wpdb; + $order_meta_table = OrdersTableDataStore::get_meta_table_name(); + $included_order_ids = implode( ',', array_map( 'absint', $order_ids ) ); + + if ( OrderUtil::custom_orders_table_usage_is_enabled() ) { + /* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */ + $order_attributions_meta = $wpdb->get_results( + "SELECT order_id, meta_key, meta_value + FROM $order_meta_table + WHERE order_id IN ({$included_order_ids}) + AND meta_key IN ( '_wc_order_attribution_source_type', '_wc_order_attribution_utm_source' ) + ", + ARRAY_A + ); + /* phpcs:enable */ + } else { + /* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared */ + $order_attributions_meta = $wpdb->get_results( + "SELECT post_id as order_id, meta_key, meta_value + FROM $wpdb->postmeta + WHERE post_id IN ({$included_order_ids}) + AND meta_key IN ( '_wc_order_attribution_source_type', '_wc_order_attribution_utm_source' ) + ", + ARRAY_A + ); + /* phpcs:enable */ + } + + $order_attributions = array(); + foreach ( $order_attributions_meta as $meta ) { + if ( ! isset( $order_attributions[ $meta['order_id'] ] ) ) { + $order_attributions[ $meta['order_id'] ] = array(); + } + $order_attributions[ $meta['order_id'] ][ $meta['meta_key'] ] = $meta['meta_value']; + } + + return $order_attributions; + } + /** * Get all statuses that have been synced. * diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php index c2d3a552efa7c..e99e7bfd6bbb5 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php @@ -721,7 +721,7 @@ protected static function set_customer_first_order( $customer_id, $order_id ) { $wpdb->query( $wpdb->prepare( // phpcs:ignore Generic.Commenting.Todo.TaskFound - // TODO: use the %i placeholder to prepare the table name when available in the the minimum required WordPress version. + // TODO: use the %i placeholder to prepare the table name when available in the minimum required WordPress version. // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "UPDATE {$orders_stats_table} SET returning_customer = CASE WHEN order_id = %d THEN false ELSE true END WHERE customer_id = %d", $order_id, diff --git a/plugins/woocommerce/src/Admin/API/Reports/Taxes/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Taxes/DataStore.php index a9160b996e60b..b5e5eb94587c9 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Taxes/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Taxes/DataStore.php @@ -61,18 +61,28 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { * Assign report columns once full table name has been assigned. */ protected function assign_report_columns() { - $table_name = self::get_db_table_name(); + global $wpdb; + $table_name = self::get_db_table_name(); + + // Using wp_woocommerce_tax_rates table limits the result to only the existing tax rates and + // omits the historical records which differs from the purpose of wp_wc_order_tax_lookup table. + // So in order to get the same data present in wp_woocommerce_tax_rates without breaking the + // API contract the values are now retrieved from wp_woocommerce_order_items and wp_woocommerce_order_itemmeta. + // And given that country, state and priority are not separate columns within the woocommerce_order_items, + // a split to order_item_name column value is required to separate those values. This is not ideal, + // but given this query is paginated and cached, then it is not a big deal. There is always room for + // improvements here. $this->report_columns = array( 'tax_rate_id' => "{$table_name}.tax_rate_id", - 'name' => 'tax_rate_name as name', - 'tax_rate' => 'tax_rate', - 'country' => 'tax_rate_country as country', - 'state' => 'tax_rate_state as state', - 'priority' => 'tax_rate_priority as priority', + 'name' => "SUBSTRING_INDEX(SUBSTRING_INDEX({$wpdb->prefix}woocommerce_order_items.order_item_name,'-',-2), '-', 1) as name", + 'tax_rate' => "CAST({$wpdb->prefix}woocommerce_order_itemmeta.meta_value AS DECIMAL(7,4)) as tax_rate", + 'country' => "SUBSTRING_INDEX({$wpdb->prefix}woocommerce_order_items.order_item_name,'-',1) as country", + 'state' => "SUBSTRING_INDEX(SUBSTRING_INDEX({$wpdb->prefix}woocommerce_order_items.order_item_name,'-',-3), '-', 1) as state", + 'priority' => "SUBSTRING_INDEX({$wpdb->prefix}woocommerce_order_items.order_item_name,'-',-1) as priority", 'total_tax' => 'SUM(total_tax) as total_tax', 'order_tax' => 'SUM(order_tax) as order_tax', 'shipping_tax' => 'SUM(shipping_tax) as shipping_tax', - 'orders_count' => "COUNT( DISTINCT ( CASE WHEN total_tax >= 0 THEN {$table_name}.order_id END ) ) as orders_count", + 'orders_count' => "COUNT({$table_name}.order_id) as orders_count", ); } @@ -97,11 +107,8 @@ protected function add_from_sql_params( $query_args, $order_status_filter ) { $this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}wc_order_stats ON {$table_name}.order_id = {$wpdb->prefix}wc_order_stats.order_id" ); } - if ( isset( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) { - $this->add_sql_clause( 'join', "JOIN {$wpdb->prefix}woocommerce_tax_rates ON default_results.tax_rate_id = {$wpdb->prefix}woocommerce_tax_rates.tax_rate_id" ); - } else { - $this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}woocommerce_tax_rates ON {$table_name}.tax_rate_id = {$wpdb->prefix}woocommerce_tax_rates.tax_rate_id" ); - } + $this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}woocommerce_order_items ON {$table_name}.order_id = {$wpdb->prefix}woocommerce_order_items.order_id AND {$wpdb->prefix}woocommerce_order_items.order_item_type = 'tax'" ); + $this->subquery->add_sql_clause( 'join', "JOIN {$wpdb->prefix}woocommerce_order_itemmeta ON {$wpdb->prefix}woocommerce_order_itemmeta.order_item_id = {$wpdb->prefix}woocommerce_order_items.order_item_id AND {$wpdb->prefix}woocommerce_order_itemmeta.meta_key = 'rate_percent'" ); } /** @@ -178,28 +185,6 @@ public function get_data( $query_args ) { if ( isset( $query_args['taxes'] ) && is_array( $query_args['taxes'] ) && ! empty( $query_args['taxes'] ) ) { $total_results = count( $query_args['taxes'] ); $total_pages = (int) ceil( $total_results / $params['per_page'] ); - - $inner_selections = array( 'tax_rate_id', 'total_tax', 'order_tax', 'shipping_tax', 'orders_count' ); - $outer_selections = array( 'name', 'tax_rate', 'country', 'state', 'priority' ); - - $selections = $this->selected_columns( array( 'fields' => $inner_selections ) ); - $fields = $this->get_fields( $query_args ); - $join_selections = $this->format_join_selections( $fields, array( 'tax_rate_id' ), $outer_selections ); - $ids_table = $this->get_ids_table( $query_args['taxes'], 'tax_rate_id' ); - - $this->subquery->clear_sql_clause( 'select' ); - $this->subquery->add_sql_clause( 'select', $this->selected_columns( array( 'fields' => $inner_selections ) ) ); - $this->add_sql_clause( 'select', $join_selections ); - $this->add_sql_clause( 'from', '(' ); - $this->add_sql_clause( 'from', $this->subquery->get_query_statement() ); - $this->add_sql_clause( 'from', ") AS {$table_name}" ); - $this->add_sql_clause( - 'right_join', - "RIGHT JOIN ( {$ids_table} ) AS default_results - ON default_results.tax_rate_id = {$table_name}.tax_rate_id" - ); - - $taxes_query = $this->get_query_statement(); } else { $db_records_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM ( @@ -213,13 +198,15 @@ public function get_data( $query_args ) { if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) { return $data; } - - $this->subquery->clear_sql_clause( 'select' ); - $this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) ); - $this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) ); - $taxes_query = $this->subquery->get_query_statement(); } + $this->subquery->clear_sql_clause( 'select' ); + $this->subquery->add_sql_clause( 'select', $this->selected_columns( $query_args ) ); + $this->subquery->add_sql_clause( 'group_by', ", {$wpdb->prefix}woocommerce_order_items.order_item_name, {$wpdb->prefix}woocommerce_order_itemmeta.meta_value" ); + $this->subquery->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) ); + + $taxes_query = $this->subquery->get_query_statement(); + $tax_data = $wpdb->get_results( $taxes_query, ARRAY_A @@ -253,9 +240,9 @@ protected function normalize_order_by( $order_by ) { global $wpdb; if ( 'tax_code' === $order_by ) { - return 'CONCAT_WS( "-", NULLIF(tax_rate_country, ""), NULLIF(tax_rate_state, ""), NULLIF(tax_rate_name, ""), NULLIF(tax_rate_priority, "") )'; + return "{$wpdb->prefix}woocommerce_order_items.order_item_name"; } elseif ( 'rate' === $order_by ) { - return "CAST({$wpdb->prefix}woocommerce_tax_rates.tax_rate as DECIMAL(7,4))"; + return 'tax_rate'; } return $order_by; diff --git a/plugins/woocommerce/src/Admin/Features/LaunchYourStore.php b/plugins/woocommerce/src/Admin/Features/LaunchYourStore.php index 09ca19607df4d..80865f089d54f 100644 --- a/plugins/woocommerce/src/Admin/Features/LaunchYourStore.php +++ b/plugins/woocommerce/src/Admin/Features/LaunchYourStore.php @@ -4,22 +4,22 @@ use Automattic\WooCommerce\Admin\PageController; use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; +use Automattic\WooCommerce\Admin\WCAdminHelper; /** * Takes care of Launch Your Store related actions. */ class LaunchYourStore { + const BANNER_DISMISS_USER_META_KEY = 'woocommerce_coming_soon_banner_dismissed'; /** * Constructor. */ public function __construct() { add_action( 'woocommerce_update_options_site-visibility', array( $this, 'save_site_visibility_options' ) ); - add_action( 'current_screen', array( $this, 'maybe_create_coming_soon_page' ) ); - if ( is_admin() ) { - add_filter( 'woocommerce_admin_shared_settings', array( $this, 'preload_settings' ) ); - } + add_filter( 'woocommerce_admin_shared_settings', array( $this, 'preload_settings' ) ); add_action( 'wp_footer', array( $this, 'maybe_add_coming_soon_banner_on_frontend' ) ); add_action( 'init', array( $this, 'register_launch_your_store_user_meta_fields' ) ); + add_action( 'wp_login', array( $this, 'reset_woocommerce_coming_soon_banner_dismissed' ), 10, 2 ); } /** @@ -39,10 +39,6 @@ public function save_site_visibility_options() { 'woocommerce_private_link' => array( 'yes', 'no' ), ); - if ( isset( $_POST['woocommerce_store_pages_only'] ) ) { - $this->possibly_update_coming_soon_page( wc_clean( wp_unslash( $_POST['woocommerce_store_pages_only'] ) ) ); - } - $at_least_one_saved = false; foreach ( $options as $name => $option ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized @@ -58,143 +54,6 @@ public function save_site_visibility_options() { } } - /** - * Update the contents of the coming soon page on settings change. Do not update if the post request - * doesn't change the store pages only setting, if the setting is unchanged, or if the page has been edited. - * - * @param string $next_store_pages_only The next store pages only setting. - * @return void - */ - public function possibly_update_coming_soon_page( $next_store_pages_only ) { - $option_name = 'woocommerce_store_pages_only'; - $current_store_pages_only = get_option( $option_name, null ); - - // If the current and next store pages only values are the same, return. - if ( $current_store_pages_only && $current_store_pages_only === $next_store_pages_only ) { - return; - } - - $page_id = get_option( 'woocommerce_coming_soon_page_id' ); - $page = get_post( $page_id ); - $original_page_content = 'yes' === $current_store_pages_only - ? $this->get_store_only_coming_soon_content() - : $this->get_entire_site_coming_soon_content(); - - // If the page exists and the content is not the same as the original content, its been edited from its original state. Return early to respect any changes. - if ( $page && $page->post_content !== $original_page_content ) { - return; - } - - if ( $page_id ) { - $next_page_content = 'yes' === $next_store_pages_only - ? $this->get_store_only_coming_soon_content() - : $this->get_entire_site_coming_soon_content(); - wp_update_post( - array( - 'ID' => $page_id, - 'post_content' => $next_page_content, - ) - ); - - $template_id = 'yes' === $next_store_pages_only - ? 'coming-soon-store-only' - : 'coming-soon-entire-site'; - update_post_meta( $page_id, '_wp_page_template', $template_id ); - } - } - - /** - * Create a pattern for the store only coming soon page. - * - * @return string - */ - public function get_store_only_coming_soon_content() { - $heading = __( 'Great things coming soon', 'woocommerce' ); - $subheading = __( 'Something big is brewing! Our store is in the works - Launching shortly!', 'woocommerce' ); - - return sprintf( - ' -
- - - - -

%s

- - - - - - - -

%s

- - - - -
- ', - $heading, - $subheading - ); - } - - /** - * Create a pattern for the entire site coming soon page. - * - * @return string - */ - public function get_entire_site_coming_soon_content() { - $heading = __( 'Pardon our dust! We\'re working on something amazing -- check back soon!', 'woocommerce' ); - - return sprintf( - ' -
- - - - -

%s

- - - - -
- ', - $heading - ); - } - - /** - * Add `coming soon` page when it hasn't been created yet. - * - * @param WP_Screen $current_screen Current screen object. - * - * @return void - */ - public function maybe_create_coming_soon_page( $current_screen ) { - $option_name = 'woocommerce_coming_soon_page_id'; - $current_page = PageController::get_instance()->get_current_page(); - $is_home = isset( $current_page['id'] ) && 'woocommerce-home' === $current_page['id']; - $page_id_option = get_option( $option_name, false ); - if ( $current_screen && 'woocommerce_page_wc-admin' === $current_screen->id && $is_home && ! $page_id_option ) { - $store_pages_only = 'yes' === get_option( 'woocommerce_store_pages_only', 'no' ); - $page_id = wc_create_page( - esc_sql( _x( 'Coming Soon', 'Page slug', 'woocommerce' ) ), - $option_name, - _x( 'Coming Soon', 'Page title', 'woocommerce' ), - $store_pages_only ? $this->get_store_only_coming_soon_content() : $this->get_entire_site_coming_soon_content(), - ); - $template_id = $store_pages_only ? 'coming-soon-store-only' : 'coming-soon-entire-site'; - update_post_meta( $page_id, '_wp_page_template', $template_id ); - // wc_create_page doesn't create options with autoload = yes. - // Since we'll querying the option on WooCommerce home, - // we should update the option to set autoload to yes. - $page_id_option = get_option( $option_name ); - update_option( $option_name, $page_id_option, true ); - } - } - /** * Preload settings for Site Visibility. * @@ -211,6 +70,9 @@ public function preload_settings( $settings ) { $is_setting_page = $current_screen && 'woocommerce_page_wc-settings' === $current_screen->id; if ( $is_setting_page ) { + // Regnerate the share key if it's not set. + add_option( 'woocommerce_share_key', wp_generate_password( 32, false ) ); + $settings['siteVisibilitySettings'] = array( 'shop_permalink' => get_permalink( wc_get_page_id( 'shop' ) ), 'woocommerce_coming_soon' => get_option( 'woocommerce_coming_soon' ), @@ -223,11 +85,26 @@ public function preload_settings( $settings ) { return $settings; } + /** + * User must be an admin or editor. + * + * @return bool + */ + private function is_manager_or_admin() { + // phpcs:ignore + if ( ! current_user_can( 'shop_manager' ) && ! current_user_can( 'administrator' ) ) { + return false; + } + + return true; + } + /** * Add 'coming soon' banner on the frontend when the following conditions met. * * - User must be either an admin or store editor (must be logged in). * - 'woocommerce_coming_soon' option value must be 'yes' + * - The page must not be the Coming soon page itself. */ public function maybe_add_coming_soon_banner_on_frontend() { // Do not show the banner if the site is being previewed. @@ -235,9 +112,16 @@ public function maybe_add_coming_soon_banner_on_frontend() { return false; } - // User must be an admin or editor. - // phpcs:ignore - if ( ! current_user_can( 'shop_manager' ) && ! current_user_can( 'administrator' ) ) { + $current_user_id = get_current_user_id(); + if ( ! $current_user_id ) { + return false; + } + + if ( get_user_meta( $current_user_id, self::BANNER_DISMISS_USER_META_KEY, true ) === 'yes' ) { + return false; + } + + if ( ! $this->is_manager_or_admin() ) { return false; } @@ -246,26 +130,37 @@ public function maybe_add_coming_soon_banner_on_frontend() { return false; } - $link = admin_url( 'admin.php?page=wc-settings#wc_settings_general_site_visibility_slotfill' ); + $store_pages_only = get_option( 'woocommerce_store_pages_only' ) === 'yes'; + if ( $store_pages_only && ! WCAdminHelper::is_store_page() ) { + return false; + } + + $link = admin_url( 'admin.php?page=wc-settings&tab=site-visibility' ); + $rest_url = rest_url( 'wp/v2/users/' . $current_user_id ); + $rest_nonce = wp_create_nonce( 'wp_rest' ); $text = sprintf( // translators: no need to translate it. It's a link. __( " - This page is in \"Coming soon\" mode and is only visible to you and those who have permission. To make it public to everyone, change visibility settings. + This page is in \"Coming soon\" mode and is only visible to you and those who have permission. To make it public to everyone, change visibility settings ", 'woocommerce' ), $link ); // phpcs:ignore - echo ""; + echo ""; } /** * Register user meta fields for Launch Your Store. */ public function register_launch_your_store_user_meta_fields() { + if ( ! $this->is_manager_or_admin() ) { + return; + } + register_meta( 'user', 'woocommerce_launch_your_store_tour_hidden', @@ -276,5 +171,31 @@ public function register_launch_your_store_user_meta_fields() { 'show_in_rest' => true, ) ); + + register_meta( + 'user', + self::BANNER_DISMISS_USER_META_KEY, + array( + 'type' => 'string', + 'description' => 'Indicate whether the user has dismissed the coming soon notice or not.', + 'single' => true, + 'show_in_rest' => true, + ) + ); + } + + /** + * Reset 'woocommerce_coming_soon_banner_dismissed' user meta to 'no'. + * + * Runs when a user logs-in successfully. + * + * @param string $user_login user login. + * @param object $user user object. + */ + public function reset_woocommerce_coming_soon_banner_dismissed( $user_login, $user ) { + $existing_meta = get_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, true ); + if ( 'yes' === $existing_meta ) { + update_user_meta( $user->ID, self::BANNER_DISMISS_USER_META_KEY, 'no' ); + } } } diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php index 07bdc4c08ec5e..ec29b5a591e59 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php @@ -416,7 +416,8 @@ public function get_json() { $tasks_json = array(); // We have no use for hidden lists, it's expensive to compute individual tasks completion. - if ( $this->is_visible() ) { + // Exception: Secret tasklist is always hidden. + if ( $this->is_visible() || 'secret_tasklist' === $this->id ) { foreach ( $this->tasks as $task ) { $json = $task->get_json(); if ( $json['canView'] ) { diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/CustomizeStore.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/CustomizeStore.php index c71647403a072..432bd4e20e49c 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/CustomizeStore.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/CustomizeStore.php @@ -108,7 +108,7 @@ public function can_view() { * @return string */ public function get_action_url() { - return admin_url( 'wp-admin/admin.php?page=wc-admin&path=%2Fcustomize-store' ); + return admin_url( 'admin.php?page=wc-admin&path=%2Fcustomize-store' ); } diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/LaunchYourStore.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/LaunchYourStore.php index eafcd55ca1ae6..cbbcb38b4f015 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/LaunchYourStore.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/LaunchYourStore.php @@ -65,7 +65,7 @@ public function get_time() { * @return string */ public function get_action_url() { - return admin_url( 'wp-admin/admin.php?page=wc-admin&path=%2Flaunch-your-store' ); + return admin_url( 'admin.php?page=wc-admin&path=%2Flaunch-your-store' ); } /** @@ -74,15 +74,7 @@ public function get_action_url() { * @return bool */ public function is_complete() { - $launch_status = get_option( 'launch-status' ); - - // The site is launched when the launch status is 'launched' or missing. - $launched_values = array( - 'launched', - '', - false, - ); - return in_array( $launch_status, $launched_values, true ); + return 'yes' !== get_option( 'woocommerce_coming_soon' ); } /** diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php index 3d0824e4033fc..b9824bf11a18c 100644 --- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php +++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php @@ -4,6 +4,7 @@ use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; use Automattic\WooCommerce\Internal\Admin\WCAdminAssets; +use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile; /** * Products Task @@ -43,6 +44,12 @@ public function get_id() { * @return string */ public function get_title() { + $onboarding_profile = get_option( OnboardingProfile::DATA_OPTION, array() ); + + if ( isset( $onboarding_profile['business_choice'] ) && 'im_already_selling' === $onboarding_profile['business_choice'] ) { + return __( 'Import your products', 'woocommerce' ); + } + return __( 'Add your products', 'woocommerce' ); } diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php index a78aa340e89a4..f2579ffc997d7 100644 --- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php +++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php @@ -79,6 +79,7 @@ public function __construct() { add_action( 'rest_api_init', array( $this, 'register_user_metas' ) ); add_filter( 'register_block_type_args', array( $this, 'register_metadata_attribute' ) ); + add_filter( 'woocommerce_get_block_types', array( $this, 'get_block_types' ), 999, 1 ); // Make sure the block registry is initialized so that core blocks are registered. BlockRegistry::get_instance(); @@ -115,6 +116,9 @@ public function enqueue_scripts() { ); wp_tinymce_inline_scripts(); wp_enqueue_media(); + wp_register_style( 'wc-global-presets', false ); // phpcs:ignore + wp_add_inline_style( 'wc-global-presets', wp_get_global_stylesheet( array( 'presets' ) ) ); + wp_enqueue_style( 'wc-global-presets' ); } /** @@ -446,4 +450,19 @@ public function register_metadata_attribute( $args ) { return $args; } + + /** + * Filters woocommerce block types. + * + * @param string[] $block_types Array of woocommerce block types. + * @return array + */ + public function get_block_types( $block_types ) { + if ( PageController::is_admin_page() ) { + // Ignore all woocommerce blocks. + return array(); + } + + return $block_types; + } } diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplate.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplate.php index f1e6532958b8a..49f88ad6b48be 100644 --- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplate.php +++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/ProductTemplate.php @@ -61,6 +61,13 @@ class ProductTemplate { */ private $icon = null; + /** + * If the template is directly selectable through the UI. + * + * @var boolean + */ + private $is_selectable_by_user = true; + /** * ProductTemplate constructor * @@ -86,6 +93,10 @@ public function __construct( array $data ) { if ( isset( $data['icon'] ) ) { $this->icon = $data['icon']; } + + if ( isset( $data['is_selectable_by_user'] ) ) { + $this->is_selectable_by_user = $data['is_selectable_by_user']; + } } /** @@ -180,6 +191,15 @@ public function get_order() { return $this->order; } + /** + * Get the selectable attribute. + * + * @return boolean Selectable. + */ + public function get_is_selectable_by_user() { + return $this->is_selectable_by_user; + } + /** * Set the template order. * @@ -196,13 +216,14 @@ public function set_order( int $order ) { */ public function to_json() { return array( - 'id' => $this->get_id(), - 'title' => $this->get_title(), - 'description' => $this->get_description(), - 'icon' => $this->get_icon(), - 'order' => $this->get_order(), - 'layoutTemplateId' => $this->get_layout_template_id(), - 'productData' => $this->get_product_data(), + 'id' => $this->get_id(), + 'title' => $this->get_title(), + 'description' => $this->get_description(), + 'icon' => $this->get_icon(), + 'order' => $this->get_order(), + 'layoutTemplateId' => $this->get_layout_template_id(), + 'productData' => $this->get_product_data(), + 'isSelectableByUser' => $this->get_is_selectable_by_user(), ); } } diff --git a/plugins/woocommerce/src/Admin/PluginsHelper.php b/plugins/woocommerce/src/Admin/PluginsHelper.php index 3deb9589856bc..ce7b0ee844d47 100644 --- a/plugins/woocommerce/src/Admin/PluginsHelper.php +++ b/plugins/woocommerce/src/Admin/PluginsHelper.php @@ -13,7 +13,10 @@ use Automatic_Upgrader_Skin; use Automattic\WooCommerce\Admin\PluginsInstallLoggers\AsyncPluginsInstallLogger; use Automattic\WooCommerce\Admin\PluginsInstallLoggers\PluginsInstallLogger; +use Automattic\WooCommerce\Internal\Admin\WCAdminAssets; use Plugin_Upgrader; +use WC_Helper; +use WC_Helper_Updater; use WP_Error; use WP_Upgrader; @@ -35,6 +38,8 @@ public static function init() { add_action( 'woocommerce_plugins_install_callback', array( __CLASS__, 'install_plugins' ), 10, 2 ); add_action( 'woocommerce_plugins_install_and_activate_async_callback', array( __CLASS__, 'install_and_activate_plugins_async_callback' ), 10, 2 ); add_action( 'woocommerce_plugins_activate_callback', array( __CLASS__, 'activate_plugins' ), 10, 2 ); + add_action( 'admin_notices', array( __CLASS__, 'maybe_show_connect_notice_in_plugin_list' ) ); + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_enqueue_scripts_for_connect_notice' ) ); } /** @@ -532,4 +537,67 @@ public static function get_activation_status( $job_id = null ) { return self::get_action_data( $actions ); } + /** + * Show notices to connect to woocommerce.com for unconnected store in the plugin list. + * + * @return void + */ + public static function maybe_show_connect_notice_in_plugin_list() { + if ( 'woocommerce_page_wc-settings' !== get_current_screen()->id ) { + return; + } + + $notice_type = WC_Helper_Updater::get_woo_connect_notice_type(); + + if ( 'none' === $notice_type ) { + return; + } + + $notice_string = ''; + + if ( 'long' === $notice_type ) { + $notice_string .= __( 'Your store might be at risk as you are running old versions of WooCommerce plugins.', 'woocommerce' ); + $notice_string .= ' '; + } + + $connect_page_url = add_query_arg( + array( + 'page' => 'wc-admin', + 'tab' => 'my-subscriptions', + 'path' => rawurlencode( '/extensions' ), + ), + admin_url( 'admin.php' ) + ); + + $notice_string .= sprintf( + /* translators: %s: Connect page URL */ + __( 'Connect your store to WooCommerce.com to get updates and streamlined support for your subscriptions.', 'woocommerce' ), + esc_url( $connect_page_url ) + ); + + echo '
+

' . wp_kses_post( $notice_string ) . '

+
'; + } + + /** + * Enqueue scripts for connect notice. + * + * @return void + */ + public static function maybe_enqueue_scripts_for_connect_notice() { + if ( 'woocommerce_page_wc-settings' !== get_current_screen()->id ) { + return; + } + + $notice_type = WC_Helper_Updater::get_woo_connect_notice_type(); + + if ( 'none' === $notice_type ) { + return; + } + + WCAdminAssets::register_script( 'wp-admin-scripts', 'woo-connect-notice' ); + wp_enqueue_script( 'woo-connect-notice' ); + } + } diff --git a/plugins/woocommerce/src/Admin/WCAdminHelper.php b/plugins/woocommerce/src/Admin/WCAdminHelper.php index 4295b69e80d62..15e9b71615a5f 100644 --- a/plugins/woocommerce/src/Admin/WCAdminHelper.php +++ b/plugins/woocommerce/src/Admin/WCAdminHelper.php @@ -128,7 +128,6 @@ public static function is_site_fresh() { * * Store pages are defined as: * - * - My Account * - Shop * - Cart * - Checkout @@ -153,12 +152,12 @@ public static function is_store_page( $url = '' ) { // WC store pages. $store_pages = array( - 'myaccount' => wc_get_page_id( 'myaccount' ), - 'shop' => wc_get_page_id( 'shop' ), - 'cart' => wc_get_page_id( 'cart' ), - 'checkout' => wc_get_page_id( 'checkout' ), - 'privacy' => wc_privacy_policy_page_id(), - 'terms' => wc_terms_and_conditions_page_id(), + 'shop' => wc_get_page_id( 'shop' ), + 'cart' => wc_get_page_id( 'cart' ), + 'checkout' => wc_get_page_id( 'checkout' ), + 'privacy' => wc_privacy_policy_page_id(), + 'terms' => wc_terms_and_conditions_page_id(), + 'coming_soon' => wc_get_page_id( 'coming_soon' ), ); /** @@ -237,7 +236,7 @@ public static function is_store_page( $url = '' ) { private static function get_normalized_url_path( $url ) { $query = wp_parse_url( $url, PHP_URL_QUERY ); $path = wp_parse_url( $url, PHP_URL_PATH ) . ( $query ? '?' . $query : '' ); - $home_path = wp_parse_url( site_url(), PHP_URL_PATH ); + $home_path = wp_parse_url( site_url(), PHP_URL_PATH ) ?? ''; $normalized_path = trim( substr( $path, strlen( $home_path ) ), '/' ); return $normalized_path; } diff --git a/plugins/woocommerce/src/Blocks/AI/Connection.php b/plugins/woocommerce/src/Blocks/AI/Connection.php index a08f8c6af2763..cedd08b8cd22d 100644 --- a/plugins/woocommerce/src/Blocks/AI/Connection.php +++ b/plugins/woocommerce/src/Blocks/AI/Connection.php @@ -13,6 +13,7 @@ */ class Connection { const TEXT_COMPLETION_API_URL = 'https://public-api.wordpress.com/wpcom/v2/text-completion'; + const MODEL = 'gpt-3.5-turbo-1106'; /** * The post request. @@ -33,6 +34,7 @@ public function fetch_ai_response( $token, $prompt, $timeout = 15, $response_for 'feature' => 'woocommerce_blocks_patterns', 'prompt' => $prompt, 'token' => $token, + 'model' => self::MODEL, ); if ( $response_format ) { @@ -77,6 +79,7 @@ public function fetch_ai_responses( $token, array $prompts, $timeout = 15, $resp 'feature' => 'woocommerce_blocks_patterns', 'prompt' => $prompt, 'token' => $token, + 'model' => self::MODEL, ); if ( $response_format ) { diff --git a/plugins/woocommerce/src/Blocks/AIContent/PatternsHelper.php b/plugins/woocommerce/src/Blocks/AIContent/PatternsHelper.php index f50d61b7a7f5d..26d8fb28b44b0 100644 --- a/plugins/woocommerce/src/Blocks/AIContent/PatternsHelper.php +++ b/plugins/woocommerce/src/Blocks/AIContent/PatternsHelper.php @@ -35,16 +35,14 @@ public static function get_image_url( $images, $index, $default_image ) { * @return \WP_Post|null */ public static function get_patterns_ai_data_post() { - $arg = array( - 'post_type' => 'patterns_ai_data', - 'posts_per_page' => 1, - 'no_found_rows' => true, - 'cache_results' => true, + $posts = get_posts( + array( + 'post_type' => 'patterns_ai_data', + 'posts_per_page' => 1, + 'cache_results' => true, + ) ); - $query = new \WP_Query( $arg ); - - $posts = $query->get_posts(); return $posts[0] ?? null; } @@ -106,13 +104,17 @@ public static function get_patterns_dictionary( $pattern_slug = null ) { return new WP_Error( 'json_decode_error', __( 'Error decoding JSON.', 'woocommerce' ) ); } - $patterns_ai_data_post = self::get_patterns_ai_data_post(); $patterns_dictionary = ''; - if ( ! empty( $patterns_ai_data_post->post_content ) ) { - $patterns_dictionary = json_decode( $patterns_ai_data_post->post_content, true ); + $ai_connection_allowed = get_option( 'woocommerce_blocks_allow_ai_connection' ); + + if ( $ai_connection_allowed ) { + $patterns_ai_data_post = self::get_patterns_ai_data_post(); + if ( ! empty( $patterns_ai_data_post->post_content ) ) { + $patterns_dictionary = json_decode( $patterns_ai_data_post->post_content, true ); - if ( json_last_error() !== JSON_ERROR_NONE ) { - return new WP_Error( 'json_decode_error', __( 'Error decoding JSON.', 'woocommerce' ) ); + if ( json_last_error() !== JSON_ERROR_NONE ) { + return new WP_Error( 'json_decode_error', __( 'Error decoding JSON.', 'woocommerce' ) ); + } } } diff --git a/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php b/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php index f6fa7a82c9df9..c1c6824aeba0a 100644 --- a/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php +++ b/plugins/woocommerce/src/Blocks/Assets/AssetDataRegistry.php @@ -80,24 +80,25 @@ protected function init() { */ protected function get_core_data() { return [ - 'adminUrl' => admin_url(), - 'countries' => WC()->countries->get_countries(), - 'currency' => $this->get_currency_data(), - 'currentUserId' => get_current_user_id(), - 'currentUserIsAdmin' => current_user_can( 'manage_woocommerce' ), - 'dateFormat' => wc_date_format(), - 'homeUrl' => esc_url( home_url( '/' ) ), - 'locale' => $this->get_locale_data(), - 'dashboardUrl' => wc_get_account_endpoint_url( 'dashboard' ), - 'orderStatuses' => $this->get_order_statuses(), - 'placeholderImgSrc' => wc_placeholder_img_src(), - 'productsSettings' => $this->get_products_settings(), - 'siteTitle' => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), - 'storePages' => $this->get_store_pages(), - 'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ), - 'wcVersion' => defined( 'WC_VERSION' ) ? WC_VERSION : '', - 'wpLoginUrl' => wp_login_url(), - 'wpVersion' => get_bloginfo( 'version' ), + 'adminUrl' => admin_url(), + 'countries' => WC()->countries->get_countries(), + 'currency' => $this->get_currency_data(), + 'currentUserId' => get_current_user_id(), + 'currentUserIsAdmin' => current_user_can( 'manage_woocommerce' ), + 'currentThemeIsFSETheme' => wc_current_theme_is_fse_theme(), + 'dateFormat' => wc_date_format(), + 'homeUrl' => esc_url( home_url( '/' ) ), + 'locale' => $this->get_locale_data(), + 'dashboardUrl' => wc_get_account_endpoint_url( 'dashboard' ), + 'orderStatuses' => $this->get_order_statuses(), + 'placeholderImgSrc' => wc_placeholder_img_src(), + 'productsSettings' => $this->get_products_settings(), + 'siteTitle' => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), + 'storePages' => $this->get_store_pages(), + 'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ), + 'wcVersion' => defined( 'WC_VERSION' ) ? WC_VERSION : '', + 'wpLoginUrl' => wp_login_url(), + 'wpVersion' => get_bloginfo( 'version' ), ]; } @@ -332,7 +333,7 @@ public function hydrate_api_request( $path ) { public function hydrate_data_from_api_request( $key, $path, $check_key_exists = false ) { $this->add( $key, - function() use ( $path ) { + function () use ( $path ) { if ( isset( $this->preloaded_api_requests[ $path ], $this->preloaded_api_requests[ $path ]['body'] ) ) { return $this->preloaded_api_requests[ $path ]['body']; } diff --git a/plugins/woocommerce/src/Blocks/AssetsController.php b/plugins/woocommerce/src/Blocks/AssetsController.php index e056062415cd4..01cd1228a9e9e 100644 --- a/plugins/woocommerce/src/Blocks/AssetsController.php +++ b/plugins/woocommerce/src/Blocks/AssetsController.php @@ -50,6 +50,7 @@ public function register_assets() { $this->register_style( 'wc-blocks-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks', 'css' ), dirname( __DIR__ ) ), array(), 'all', true ); $this->register_style( 'wc-blocks-editor-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-editor-style', 'css' ), dirname( __DIR__ ) ), array( 'wp-edit-blocks' ), 'all', true ); + $this->api->register_script( 'wc-types', $this->api->get_block_asset_build_path( 'wc-types' ), array(), false ); $this->api->register_script( 'wc-blocks-middleware', 'assets/client/blocks/wc-blocks-middleware.js', array(), false ); $this->api->register_script( 'wc-blocks-data-store', 'assets/client/blocks/wc-blocks-data.js', array( 'wc-blocks-middleware' ) ); $this->api->register_script( 'wc-blocks-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-vendors' ), array(), false ); diff --git a/plugins/woocommerce/src/Blocks/BlockPatterns.php b/plugins/woocommerce/src/Blocks/BlockPatterns.php index 6da7870d27ebd..fbee02361499a 100644 --- a/plugins/woocommerce/src/Blocks/BlockPatterns.php +++ b/plugins/woocommerce/src/Blocks/BlockPatterns.php @@ -1,6 +1,7 @@ get_site_id(); if ( is_wp_error( $site_id ) ) { - return update_option( 'woocommerce_blocks_allow_ai_connection', false ); + return update_option( 'woocommerce_blocks_allow_ai_connection', false, true ); } $token = $ai_connection->get_jwt_token( $site_id ); if ( is_wp_error( $token ) ) { - return update_option( 'woocommerce_blocks_allow_ai_connection', false ); + return update_option( 'woocommerce_blocks_allow_ai_connection', false, true ); } - return update_option( 'woocommerce_blocks_allow_ai_connection', true ); + return update_option( 'woocommerce_blocks_allow_ai_connection', true, true ); } /** @@ -119,6 +120,7 @@ public function register_block_patterns() { 'keywords' => 'Keywords', 'blockTypes' => 'Block Types', 'inserter' => 'Inserter', + 'featureFlag' => 'Feature Flag', ); if ( ! file_exists( $this->patterns_path ) ) { @@ -170,6 +172,10 @@ public function register_block_patterns() { continue; } + if ( $pattern_data['featureFlag'] && ! Features::is_enabled( $pattern_data['featureFlag'] ) ) { + continue; + } + // Title is a required property. if ( ! $pattern_data['title'] ) { _doing_it_wrong( diff --git a/plugins/woocommerce/src/Blocks/BlockTemplatesController.php b/plugins/woocommerce/src/Blocks/BlockTemplatesController.php index 62cc18d0f86d3..7ef1fb8559416 100644 --- a/plugins/woocommerce/src/Blocks/BlockTemplatesController.php +++ b/plugins/woocommerce/src/Blocks/BlockTemplatesController.php @@ -3,6 +3,7 @@ use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate; use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; +use Automattic\WooCommerce\Blocks\Templates\ComingSoonTemplate; /** * BlockTypesController class. @@ -26,9 +27,9 @@ public function init() { add_filter( 'pre_get_block_file_template', array( $this, 'get_block_file_template' ), 10, 3 ); add_filter( 'get_block_template', array( $this, 'add_block_template_details' ), 10, 3 ); add_filter( 'get_block_templates', array( $this, 'add_block_templates' ), 10, 3 ); - add_filter( 'current_theme_supports-block-templates', array( $this, 'remove_block_template_support_for_shop_page' ) ); add_filter( 'taxonomy_template_hierarchy', array( $this, 'add_archive_product_to_eligible_for_fallback_templates' ), 10, 1 ); add_action( 'after_switch_theme', array( $this, 'check_should_use_blockified_product_grid_templates' ), 10, 2 ); + add_filter( 'post_type_archive_title', array( $this, 'update_product_archive_title' ), 10, 2 ); if ( wc_current_theme_is_fse_theme() ) { // By default, the Template Part Block only supports template parts that are in the current theme directory. @@ -306,19 +307,7 @@ public function get_block_file_template( $template, $id, $template_type ) { * @return WP_Block_Template|null */ public function add_block_template_details( $block_template, $id, $template_type ) { - if ( ! $block_template ) { - return $block_template; - } - if ( ! BlockTemplateUtils::template_has_title( $block_template ) ) { - $block_template->title = BlockTemplateUtils::get_block_template_title( $block_template->slug ); - } - if ( ! $block_template->description ) { - $block_template->description = BlockTemplateUtils::get_block_template_description( $block_template->slug ); - } - if ( ! $block_template->area || 'uncategorized' === $block_template->area ) { - $block_template->area = BlockTemplateUtils::get_block_template_area( $block_template->slug, $template_type ); - } - return $block_template; + return BlockTemplateUtils::update_template_data( $block_template, $template_type ); } /** @@ -330,12 +319,13 @@ public function add_block_template_details( $block_template, $id, $template_type * @return array */ public function add_block_templates( $query_result, $query, $template_type ) { - if ( ! BlockTemplateUtils::supports_block_templates( $template_type ) ) { + $slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : array(); + + if ( ! BlockTemplateUtils::supports_block_templates( $template_type ) && ! in_array( ComingSoonTemplate::SLUG, $slugs, true ) ) { return $query_result; } $post_type = isset( $query['post_type'] ) ? $query['post_type'] : ''; - $slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : array(); $template_files = $this->get_block_templates( $slugs, $template_type ); $theme_slug = wp_get_theme()->get_stylesheet(); @@ -398,17 +388,7 @@ public function add_block_templates( $query_result, $query, $template_type ) { */ $query_result = array_map( function ( $template ) use ( $template_type ) { - if ( ! BlockTemplateUtils::template_has_title( $template ) ) { - $template->title = BlockTemplateUtils::get_block_template_title( $template->slug ); - } - if ( ! $template->description ) { - $template->description = BlockTemplateUtils::get_block_template_description( $template->slug ); - } - if ( ! $template->area || 'uncategorized' === $template->area ) { - $template->area = BlockTemplateUtils::get_block_template_area( $template->slug, $template_type ); - } - - return $template; + return BlockTemplateUtils::update_template_data( $template, $template_type ); }, $query_result ); @@ -553,28 +533,26 @@ public function block_template_is_available( $template_name, $template_type = 'w } /** - * Remove the template panel from the Sidebar of the Shop page because - * the Site Editor handles it. + * Update the product archive title to "Shop". * - * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/6278 + * Attention: this method is run in classic themes as well, so it + * can't be moved to the ProductCatalogTemplate class. See: + * https://github.com/woocommerce/woocommerce/pull/46429 * - * @param bool $is_support Whether the active theme supports block templates. + * @param string $post_type_name Post type 'name' label. + * @param string $post_type Post type. * - * @return bool + * @return string */ - public function remove_block_template_support_for_shop_page( $is_support ) { - global $pagenow, $post; - + public function update_product_archive_title( $post_type_name, $post_type ) { if ( - is_admin() && - 'post.php' === $pagenow && - function_exists( 'wc_get_page_id' ) && - is_a( $post, 'WP_Post' ) && - wc_get_page_id( 'shop' ) === $post->ID + function_exists( 'is_shop' ) && + is_shop() && + 'product' === $post_type ) { - return false; + return __( 'Shop', 'woocommerce' ); } - return $is_support; + return $post_type_name; } } diff --git a/plugins/woocommerce/src/Blocks/BlockTemplatesRegistry.php b/plugins/woocommerce/src/Blocks/BlockTemplatesRegistry.php index 4acc49ffef4eb..0985cd5e536ed 100644 --- a/plugins/woocommerce/src/Blocks/BlockTemplatesRegistry.php +++ b/plugins/woocommerce/src/Blocks/BlockTemplatesRegistry.php @@ -1,6 +1,7 @@ new ProductSearchResultsTemplate(), CartTemplate::SLUG => new CartTemplate(), CheckoutTemplate::SLUG => new CheckoutTemplate(), - ComingSoonEntireSiteTemplate::SLUG => new ComingSoonEntireSiteTemplate(), - ComingSoonStoreOnlyTemplate::SLUG => new ComingSoonStoreOnlyTemplate(), OrderConfirmationTemplate::SLUG => new OrderConfirmationTemplate(), SingleProductTemplate::SLUG => new SingleProductTemplate(), ); } else { $templates = array(); } + if ( Features::is_enabled( 'launch-your-store' ) ) { + $templates[ ComingSoonTemplate::SLUG ] = new ComingSoonTemplate(); + } if ( BlockTemplateUtils::supports_block_templates( 'wp_template_part' ) ) { $template_parts = array( MiniCartTemplate::SLUG => new MiniCartTemplate(), diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php b/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php index e33fae1da99da..b3cabefb20c68 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php @@ -285,6 +285,7 @@ public static function get_cart_block_types() { 'Cart', 'CartOrderSummaryTaxesBlock', 'CartOrderSummarySubtotalBlock', + 'CartOrderSummaryTotalsBlock', 'FilledCartBlock', 'EmptyCartBlock', 'CartTotalsBlock', diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CartOrderSummaryBlock.php b/plugins/woocommerce/src/Blocks/BlockTypes/CartOrderSummaryBlock.php index 48ddf627196ec..f753f1312293e 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/CartOrderSummaryBlock.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/CartOrderSummaryBlock.php @@ -11,4 +11,68 @@ class CartOrderSummaryBlock extends AbstractInnerBlock { * @var string */ protected $block_name = 'cart-order-summary-block'; + + /** + * Get the contents of the given inner block. + * + * @param string $block_name Name of the order summary inner block. + * @param string $content The content to search. + * @return array|bool + */ + private function get_inner_block_content( $block_name, $content ) { + if ( preg_match( $this->inner_block_regex( $block_name ), $content, $matches ) ) { + return $matches[0]; + } + return false; + } + + /** + * Get the regex that will return an inner block. + * + * @param string $block_name Name of the order summary inner block. + * @return string Regex pattern. + */ + private function inner_block_regex( $block_name ) { + return '/
(.*?)<\/div>/si'; + } + + /** + * Render the Cart Order Summary block. + * + * @param array $attributes Block attributes. + * @param string $content Block content. + * @param object $block Block object. + * @return string Rendered block. + */ + protected function render( $attributes, $content, $block ) { + // The order-summary-totals block was introduced as a new parent block for the totals + // (subtotal, discount, fees, shipping and taxes) blocks. + $regex_for_cart_order_summary_totals = '/
/'; + $order_summary_totals_content = '
'; + + $totals_inner_blocks = array( 'subtotal', 'discount', 'fee', 'shipping', 'taxes' ); // We want to move these blocks inside a parent 'totals' block. + + if ( preg_match( $regex_for_cart_order_summary_totals, $content ) ) { + return $content; + } + + foreach ( $totals_inner_blocks as $key => $block_name ) { + $inner_block_content = $this->get_inner_block_content( $block_name, $content ); + + if ( $inner_block_content ) { + $order_summary_totals_content .= "\n" . $inner_block_content; + + // The last block is replaced with the totals block. + if ( count( $totals_inner_blocks ) - 1 === $key ) { + $order_summary_totals_content .= '
'; + $content = preg_replace( $this->inner_block_regex( $block_name ), $order_summary_totals_content, $content ); + } else { + // Otherwise, remove the block. + $content = preg_replace( $this->inner_block_regex( $block_name ), '', $content ); + } + } + } + + return preg_replace( '/\n\n( *?)/i', '', $content ); + } } diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CartOrderSummaryTotalsBlock.php b/plugins/woocommerce/src/Blocks/BlockTypes/CartOrderSummaryTotalsBlock.php new file mode 100644 index 0000000000000..5f405ae949927 --- /dev/null +++ b/plugins/woocommerce/src/Blocks/BlockTypes/CartOrderSummaryTotalsBlock.php @@ -0,0 +1,14 @@ +post_type ) && ! empty( $post->post_name ) && 'page-checkout' !== $post->post_name && 'wp_template' === $post->post_type ) || false === has_block( 'woocommerce/checkout', $post ) ) { return; } - $pickup_location_settings = LocalPickupUtils::get_local_pickup_settings(); + $pickup_location_settings = LocalPickupUtils::get_local_pickup_settings( 'edit' ); if ( ! isset( $pickup_location_settings['title'] ) ) { return; @@ -570,6 +570,7 @@ public static function get_checkout_block_types() { 'CheckoutOrderSummaryShippingBlock', 'CheckoutOrderSummarySubtotalBlock', 'CheckoutOrderSummaryTaxesBlock', + 'CheckoutOrderSummaryTotalsBlock', 'CheckoutPaymentBlock', 'CheckoutShippingAddressBlock', 'CheckoutShippingMethodsBlock', diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutOrderSummaryBlock.php b/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutOrderSummaryBlock.php index d4d589f47285d..41a5c7664ca64 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutOrderSummaryBlock.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutOrderSummaryBlock.php @@ -11,4 +11,70 @@ class CheckoutOrderSummaryBlock extends AbstractInnerBlock { * @var string */ protected $block_name = 'checkout-order-summary-block'; + + /** + * Get the contents of the given inner block. + * + * @param string $block_name Name of the order summary inner block. + * @param string $content The content to search. + * @return array|bool + */ + private function get_inner_block_content( $block_name, $content ) { + if ( preg_match( $this->inner_block_regex( $block_name ), $content, $matches ) ) { + return $matches[0]; + } + return false; + } + + /** + * Get the regex that will return an inner block. + * + * @param string $block_name Name of the order summary inner block. + * @return string Regex pattern. + */ + private function inner_block_regex( $block_name ) { + return '/
(.*?)<\/div>/si'; + } + + /** + * Render the Checkout Order Summary block. + * + * @param array $attributes Block attributes. + * @param string $content Block content. + * @param object $block Block object. + * @return string Rendered block. + */ + protected function render( $attributes, $content, $block ) { + // The order-summary-totals block was introduced as a new parent block for the totals + // (subtotal, discount, fees, shipping and taxes) blocks. + $regex_for_checkout_order_summary_totals = '/
/'; + $order_summary_totals_content = '
'; + + // We want to move these blocks inside a parent 'totals' block. + $totals_inner_blocks = array( 'subtotal', 'discount', 'fee', 'shipping', 'taxes' ); + + if ( preg_match( $regex_for_checkout_order_summary_totals, $content ) ) { + return $content; + } + + foreach ( $totals_inner_blocks as $key => $block_name ) { + $inner_block_content = $this->get_inner_block_content( $block_name, $content ); + + if ( $inner_block_content ) { + $order_summary_totals_content .= "\n" . $inner_block_content; + + // The last block is replaced with the totals block. + if ( count( $totals_inner_blocks ) - 1 === $key ) { + $order_summary_totals_content .= '
'; + $content = preg_replace( $this->inner_block_regex( $block_name ), $order_summary_totals_content, $content ); + } else { + // Otherwise, remove the block. + $content = preg_replace( $this->inner_block_regex( $block_name ), '', $content ); + } + } + } + + // Remove empty lines. + return preg_replace( '/\n\n( *?)/i', '', $content ); + } } diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutOrderSummaryTotalsBlock.php b/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutOrderSummaryTotalsBlock.php new file mode 100644 index 0000000000000..6fd320bfa1d87 --- /dev/null +++ b/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutOrderSummaryTotalsBlock.php @@ -0,0 +1,14 @@ +register_chunk_translations( [ $this->block_name ] ); + } + + /** + * Get the frontend style handle for this block type. + * + * @return null + */ + protected function get_block_type_style() { + return null; + } + + /** + * Get the frontend script handle for this block type. + * + * @see $this->register_block_type() + * @param string $key Data to get, or default to everything. + * @return array|string|null + */ + protected function get_block_type_script( $key = null ) { + return null; + } +} diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php b/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php index 1d7bd6fad59c1..1ff697d07a749 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php @@ -41,8 +41,15 @@ class CustomerAccount extends AbstractBlock { */ protected function initialize() { parent::initialize(); - add_filter( 'hooked_block_types', array( $this, 'register_hooked_block' ), 9, 4 ); - add_filter( 'hooked_block_woocommerce/customer-account', array( $this, 'modify_hooked_block_attributes' ), 10, 5 ); + /** + * The hooked_block_{$hooked_block_type} filter was added in WordPress 6.5. + * We are the only code adding the filter 'hooked_block_woocommerce/customer-account'. + * Using has_filter() for a compatibility check won't work because add_filter() is used in the same file. + */ + if ( version_compare( get_bloginfo( 'version' ), '6.5', '>=' ) ) { + add_filter( 'hooked_block_woocommerce/customer-account', array( $this, 'modify_hooked_block_attributes' ), 10, 5 ); + add_filter( 'hooked_block_types', array( $this, 'register_hooked_block' ), 9, 4 ); + } } /** @@ -122,10 +129,13 @@ protected function render( $attributes, $content, $block ) { ), ); + // Only provide aria-label if the display style is icon only. + $aria_label = self::ICON_ONLY === $attributes['displayStyle'] ? 'aria-label="' . esc_attr( $this->render_label() ) . '"' : ''; + $label_markup = self::ICON_ONLY === $attributes['displayStyle'] ? '' : '' . wp_kses( $this->render_label(), array() ) . ''; return "'; diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AdditionalFields.php b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AdditionalFields.php index 2621e4b16d26f..5d9895ab42e54 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AdditionalFields.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AdditionalFields.php @@ -35,8 +35,8 @@ protected function render_content( $order, $permission = false, $attributes = [] $content .= $this->render_additional_fields( $controller->filter_fields_for_order_confirmation( array_merge( - $controller->get_order_additional_fields_with_values( $order, 'contact', '', 'view' ), - $controller->get_order_additional_fields_with_values( $order, 'additional', '', 'view' ), + $controller->get_order_additional_fields_with_values( $order, 'contact', 'other', 'view' ), + $controller->get_order_additional_fields_with_values( $order, 'order', 'other', 'view' ), ) ) ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AdditionalFieldsWrapper.php b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AdditionalFieldsWrapper.php index 6aa772f13fca2..16f5082880435 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AdditionalFieldsWrapper.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AdditionalFieldsWrapper.php @@ -33,7 +33,7 @@ protected function render_content( $order, $permission = false, $attributes = [] // Contact and additional fields are currently grouped in this section. $additional_fields = array_merge( Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'contact' ), - Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'additional' ) + Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'order' ) ); return empty( $additional_fields ) ? '' : $content; @@ -48,7 +48,7 @@ protected function render_content( $order, $permission = false, $attributes = [] */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); - $this->asset_data_registry->add( 'additionalFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'additional' ) ); + $this->asset_data_registry->add( 'additionalFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'order' ) ); $this->asset_data_registry->add( 'additionalContactFields', Package::container()->get( CheckoutFields::class )->get_fields_for_location( 'contact' ) ); } } diff --git a/plugins/woocommerce/src/Blocks/BlockTypesController.php b/plugins/woocommerce/src/Blocks/BlockTypesController.php index 85125c3a496be..2a07a6b274358 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypesController.php +++ b/plugins/woocommerce/src/Blocks/BlockTypesController.php @@ -225,6 +225,7 @@ protected function get_block_types() { 'CatalogSorting', 'ClassicTemplate', 'ClassicShortcode', + 'ComingSoon', 'CustomerAccount', 'FeaturedCategory', 'FeaturedProduct', @@ -349,6 +350,13 @@ protected function get_block_types() { ); } - return $block_types; + /** + * Filters the list of allowed block types. + * + * @since 9.0.0 + * + * @param array $block_types List of block types. + */ + return apply_filters( 'woocommerce_get_block_types', $block_types ); } } diff --git a/plugins/woocommerce/src/Blocks/Domain/Package.php b/plugins/woocommerce/src/Blocks/Domain/Package.php index a21722a739e9a..8ac3d4d86afd7 100644 --- a/plugins/woocommerce/src/Blocks/Domain/Package.php +++ b/plugins/woocommerce/src/Blocks/Domain/Package.php @@ -116,7 +116,7 @@ public function get_url( $relative_url = '' ) { } /** - * Returns an instance of the the FeatureGating class. + * Returns an instance of the FeatureGating class. * * @return FeatureGating */ diff --git a/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFields.php b/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFields.php index 61603dc1aea89..cab99fc5df145 100644 --- a/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFields.php +++ b/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFields.php @@ -4,6 +4,7 @@ use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; use WC_Customer; +use WC_Data; use WC_Order; use WP_Error; @@ -40,6 +41,13 @@ class CheckoutFields { */ private $supported_field_types = [ 'text', 'select', 'checkbox' ]; + /** + * Groups of fields to be saved. + * + * @var array + */ + protected $groups = [ 'billing', 'shipping', 'other' ]; + /** * Instance of the asset data registry. * @@ -52,21 +60,29 @@ class CheckoutFields { * * @var string */ - const BILLING_FIELDS_KEY = '_additional_billing_fields'; + const BILLING_FIELDS_PREFIX = '_wc_billing/'; /** * Shipping fields meta key. * * @var string */ - const SHIPPING_FIELDS_KEY = '_additional_shipping_fields'; + const SHIPPING_FIELDS_PREFIX = '_wc_shipping/'; /** * Additional fields meta key. * * @var string + * @deprecated 8.9.0 Use OTHER_FIELDS_PREFIX instead. + */ + const ADDITIONAL_FIELDS_PREFIX = '_wc_additional/'; + + /** + * Other fields meta key. + * + * @var string */ - const ADDITIONAL_FIELDS_KEY = '_additional_fields'; + const OTHER_FIELDS_PREFIX = '_wc_other/'; /** * Sets up core fields. @@ -212,9 +228,9 @@ public function __construct( AssetDataRegistry $asset_data_registry ) { $this->fields_locations = [ // omit email from shipping and billing fields. - 'address' => array_merge( \array_diff_key( array_keys( $this->core_fields ), array( 'email' ) ) ), - 'contact' => array( 'email' ), - 'additional' => [], + 'address' => array_merge( \array_diff_key( array_keys( $this->core_fields ), array( 'email' ) ) ), + 'contact' => array( 'email' ), + 'order' => [], ]; add_filter( 'woocommerce_get_country_locale_default', array( $this, 'update_default_locale_with_fields' ) ); @@ -246,7 +262,30 @@ public function add_fields_data() { * @return array */ public function add_session_meta_keys( $keys ) { - return array_merge( $keys, array( self::BILLING_FIELDS_KEY, self::SHIPPING_FIELDS_KEY, self::ADDITIONAL_FIELDS_KEY ) ); + $meta_keys = array(); + try { + foreach ( $this->get_additional_fields() as $field_key => $field ) { + if ( 'address' === $field['location'] ) { + $meta_keys[] = self::BILLING_FIELDS_PREFIX . $field_key; + $meta_keys[] = self::SHIPPING_FIELDS_PREFIX . $field_key; + } else { + $meta_keys[] = self::OTHER_FIELDS_PREFIX . $field_key; + } + } + } catch ( \Throwable $e ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error + trigger_error( + sprintf( + 'Error adding session meta keys for checkout fields. %s', + esc_attr( $e->getMessage() ) + ), + E_USER_WARNING + ); + + return $keys; + } + + return array_merge( $keys, $meta_keys ); } /** @@ -270,9 +309,9 @@ public function default_sanitize_callback( $value, $field ) { public function default_validate_callback( $value, $field ) { if ( ! empty( $field['required'] ) && empty( $value ) ) { return new WP_Error( - 'woocommerce_blocks_checkout_field_required', + 'woocommerce_required_checkout_field', sprintf( - // translators: %s is field key. + // translators: %s is field key. __( 'The field %s is required.', 'woocommerce' ), $field['id'] ) @@ -353,40 +392,46 @@ public function deregister_checkout_field( $field_id ) { // Remove the field from the additional_fields array. unset( $this->additional_fields[ $field_id ] ); } + /** * Validates the "base" options (id, label, location) and shows warnings if they're not supplied. * * @param array $options The options supplied during field registration. * @return bool false if an error was encountered, true otherwise. */ - private function validate_options( $options ) { + private function validate_options( &$options ) { if ( empty( $options['id'] ) ) { - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', 'A checkout field cannot be registered without an id.', '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', 'A checkout field cannot be registered without an id.', '8.6.0' ); return false; } // Having fewer than 2 after exploding around a / means there is no namespace. if ( count( explode( '/', $options['id'] ) ) < 2 ) { $message = sprintf( 'Unable to register field with id: "%s". %s', $options['id'], 'A checkout field id must consist of namespace/name.' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } if ( empty( $options['label'] ) ) { $message = sprintf( 'Unable to register field with id: "%s". %s', $options['id'], 'The field label is required.' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } if ( empty( $options['location'] ) ) { $message = sprintf( 'Unable to register field with id: "%s". %s', $options['id'], 'The field location is required.' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } + if ( 'additional' === $options['location'] ) { + wc_deprecated_argument( 'location', '8.9.0', 'The "additional" location is deprecated. Use "order" instead.' ); + $options['location'] = 'order'; + } + if ( ! in_array( $options['location'], array_keys( $this->fields_locations ), true ) ) { $message = sprintf( 'Unable to register field with id: "%s". %s', $options['id'], 'The field location is invalid.' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } @@ -397,7 +442,7 @@ private function validate_options( $options ) { // Check to see if field is already in the array. if ( ! empty( $this->additional_fields[ $id ] ) || in_array( $id, $this->fields_locations[ $location ], true ) ) { $message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'The field is already registered.' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } @@ -409,27 +454,27 @@ private function validate_options( $options ) { $options['type'], implode( ', ', $this->supported_field_types ) ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } } if ( ! empty( $options['sanitize_callback'] ) && ! is_callable( $options['sanitize_callback'] ) ) { $message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'The sanitize_callback must be a valid callback.' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } if ( ! empty( $options['validate_callback'] ) && ! is_callable( $options['validate_callback'] ) ) { $message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'The validate_callback must be a valid callback.' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } // Hidden fields are not supported right now. They will be registered with hidden => false. if ( ! empty( $options['hidden'] ) && true === $options['hidden'] ) { $message = sprintf( 'Registering a field with hidden set to true is not supported. The field "%s" will be registered as visible.', $id ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); // Don't return here unlike the other fields because this is not an issue that will prevent registration. } @@ -449,7 +494,7 @@ private function process_select_field( $field_data, $options ) { if ( empty( $options['options'] ) || ! is_array( $options['options'] ) ) { $message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'Fields of type "select" must have an array of "options".' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } $cleaned_options = []; @@ -459,7 +504,7 @@ private function process_select_field( $field_data, $options ) { foreach ( $options['options'] as $option ) { if ( ! isset( $option['value'] ) || ! isset( $option['label'] ) ) { $message = sprintf( 'Unable to register field with id: "%s". %s', $id, 'Fields of type "select" must have an array of "options" and each option must contain a "value" and "label" member.' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return false; } @@ -468,7 +513,7 @@ private function process_select_field( $field_data, $options ) { if ( in_array( $sanitized_value, $added_values, true ) ) { $message = sprintf( 'Duplicate key found when registering field with id: "%s". The value in each option of "select" fields must be unique. Duplicate value "%s" found. The duplicate key will be removed.', $id, $sanitized_value ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); continue; } @@ -514,7 +559,7 @@ private function process_checkbox_field( $field_data, $options ) { if ( isset( $options['required'] ) && true === $options['required'] ) { $message = sprintf( 'Registering checkbox fields as required is not supported. "%s" will be registered as optional.', $id ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); } return $field_data; @@ -537,7 +582,7 @@ private function register_field_attributes( $id, $attributes ) { if ( ! is_array( $attributes ) || 0 === count( $attributes ) ) { $message = sprintf( 'An invalid attributes value was supplied when registering field with id: "%s". %s', $id, 'Attributes must be a non-empty array.' ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); return []; } @@ -563,7 +608,7 @@ function ( $_, $key ) use ( $allowed_attributes ) { if ( count( $attributes ) !== count( $valid_attributes ) ) { $invalid_attributes = array_keys( array_diff_key( $attributes, $valid_attributes ) ); $message = sprintf( 'Invalid attribute found when registering field with id: "%s". Attributes: %s are not allowed.', $id, implode( ', ', $invalid_attributes ) ); - _doing_it_wrong( '__experimental_woocommerce_blocks_register_checkout_field', esc_html( $message ), '8.6.0' ); + _doing_it_wrong( 'woocommerce_register_additional_checkout_field', esc_html( $message ), '8.6.0' ); } // Escape attributes to remove any malicious code and return them. @@ -625,6 +670,17 @@ public function sanitize_field( $field_key, $field_value ) { $field_value = call_user_func( $field['sanitize_callback'], $field_value, $field ); } + /** + * Allow custom sanitization of an additional field. + * + * @param mixed $field_value The value of the field being sanitized. + * @param string $field_key Key of the field being sanitized. + * + * @since 8.6.0 + * @deprecated 8.7.0 Use woocommerce_sanitize_additional_field instead. + */ + $field_value = apply_filters_deprecated( '__experimental_woocommerce_blocks_sanitize_additional_field', array( $field_value, $field_key ), '8.7.0', 'woocommerce_sanitize_additional_field', 'This action has been graduated, use woocommerce_sanitize_additional_field instead.' ); + /** * Allow custom sanitization of an additional field. * @@ -633,7 +689,7 @@ public function sanitize_field( $field_key, $field_value ) { * * @since 8.7.0 */ - return apply_filters( '__experimental_woocommerce_blocks_sanitize_additional_field', $field_value, $field_key ); + return apply_filters( 'woocommerce_sanitize_additional_field', $field_value, $field_key ); } catch ( \Throwable $e ) { // One of the filters errored so skip it. This allows the checkout process to continue. @@ -674,6 +730,17 @@ public function validate_field( $field_key, $field_value ) { } } + /** + * Pass an error object to allow validation of an additional field. + * + * @param WP_Error $errors A WP_Error object that extensions may add errors to. + * @param string $field_key Key of the field being sanitized. + * @param mixed $field_value The value of the field being validated. + * + * @since 8.6.0 + * @deprecated 8.7.0 Use woocommerce_validate_additional_field instead. + */ + wc_do_deprecated_action( '__experimental_woocommerce_blocks_validate_additional_field', array( $errors, $field_key, $field_value ), '8.7.0', 'woocommerce_validate_additional_field', 'This action has been graduated, use woocommerce_validate_additional_field instead.' ); /** * Pass an error object to allow validation of an additional field. * @@ -683,7 +750,7 @@ public function validate_field( $field_key, $field_value ) { * * @since 8.7.0 */ - do_action( '__experimental_woocommerce_blocks_validate_additional_field', $errors, $field_key, $field_value ); + do_action( 'woocommerce_validate_additional_field', $errors, $field_key, $field_value ); } catch ( \Throwable $e ) { @@ -718,7 +785,7 @@ public function update_default_locale_with_fields( $locale ) { } /** - * Returns an array of fields keys for the address group. + * Returns an array of fields keys for the address location. * * @return array An array of fields keys. */ @@ -727,7 +794,7 @@ public function get_address_fields_keys() { } /** - * Returns an array of fields keys for the contact group. + * Returns an array of fields keys for the contact location. * * @return array An array of fields keys. */ @@ -736,21 +803,37 @@ public function get_contact_fields_keys() { } /** - * Returns an array of fields keys for the additional area group. + * Returns an array of fields keys for the additional area location. * * @return array An array of fields keys. + * @deprecated 8.9.0 Use get_order_fields_keys instead. */ public function get_additional_fields_keys() { - return $this->fields_locations['additional']; + wc_deprecated_function( __METHOD__, '8.9.0', 'get_order_fields_keys' ); + return $this->get_order_fields_keys(); } /** - * Returns an array of fields for a given group. + * Returns an array of fields keys for the additional area group. * - * @param string $location The location to get fields for (address|contact|additional). + * @return array An array of fields keys. + */ + public function get_order_fields_keys() { + return $this->fields_locations['order']; + } + + /** + * Returns an array of fields for a given location. + * + * @param string $location The location to get fields for (address|contact|order). * @return array An array of fields definitions. */ public function get_fields_for_location( $location ) { + if ( 'additional' === $location ) { + wc_deprecated_argument( 'location', '8.9.0', 'The "additional" location is deprecated. Use "order" instead.' ); + $location = 'order'; + } + if ( in_array( $location, array_keys( $this->fields_locations ), true ) ) { $order_fields_keys = $this->fields_locations[ $location ]; @@ -769,24 +852,57 @@ function ( $key ) use ( $order_fields_keys ) { * Validates a set of fields for a given location against custom validation rules. * * @param array $fields Array of key value pairs of field values to validate. - * @param string $location The location being validated (address|contact|additional). - * @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group. + * @param string $location The location being validated (address|contact|order). + * @param string $group The group to get the field value for (shipping|billing|other). * @return WP_Error */ - public function validate_fields_for_location( $fields, $location, $group = '' ) { + public function validate_fields_for_location( $fields, $location, $group = 'other' ) { $errors = new WP_Error(); + if ( 'additional' === $location ) { + wc_deprecated_argument( 'location', '8.9.0', 'The "additional" location is deprecated. Use "order" instead.' ); + $location = 'order'; + } + + if ( 'additional' === $group ) { + wc_deprecated_argument( 'group', '8.9.0', 'The "additional" group is deprecated. Use "other" instead.' ); + $group = 'other'; + } + try { /** * Pass an error object to allow validation of an additional field. * * @param WP_Error $errors A WP_Error object that extensions may add errors to. * @param mixed $fields List of fields (key value pairs) in this location. - * @param string $group The group of this location (shipping|billing|''). + * @param string $group The group of this location (shipping|billing|other). + * + * @since 8.6.0 + * @deprecated 8.9.0 Use woocommerce_blocks_validate_location_order_fields instead. + */ + wc_do_deprecated_action( 'woocommerce_blocks_validate_location_additional_fields', array( $errors, $fields, $group ), '8.9.0', 'woocommerce_blocks_validate_location_additional_fields', 'This action has been graduated, use woocommerce_blocks_validate_location_additional_fields instead.' ); + /** + * Pass an error object to allow validation of an additional field. + * + * @param WP_Error $errors A WP_Error object that extensions may add errors to. + * @param mixed $fields List of fields (key value pairs) in this location. + * @param string $group The group of this location (shipping|billing|other). + * + * @since 8.6.0 + * @deprecated 8.9.0 Use woocommerce_blocks_validate_location_{location}_fields instead. + */ + wc_do_deprecated_action( '__experimental_woocommerce_blocks_validate_location_' . $location . '_fields', array( $errors, $fields, $group ), '8.9.0', 'woocommerce_blocks_validate_location_' . $location . '_fields', 'This action has been graduated, use woocommerce_blocks_validate_location_' . $location . '_fields instead.' ); + + /** + * Pass an error object to allow validation of an additional field. + * + * @param WP_Error $errors A WP_Error object that extensions may add errors to. + * @param mixed $fields List of fields (key value pairs) in this location. + * @param string $group The group of this location (shipping|billing|other). * * @since 8.7.0 */ - do_action( '__experimental_woocommerce_blocks_validate_location_' . $location . '_fields', $errors, $fields, $group ); + do_action( 'woocommerce_blocks_validate_location_' . $location . '_fields', $errors, $fields, $group ); } catch ( \Throwable $e ) { @@ -813,16 +929,21 @@ public function validate_fields_for_location( $fields, $location, $group = '' ) * * @param string $key The field key. * @param mixed $value The field value. - * @param string $location The location to validate the field for (address|contact|additional). + * @param string $location The location to validate the field for (address|contact|order). * * @return true|WP_Error True if the field is valid, a WP_Error otherwise. */ public function validate_field_for_location( $key, $value, $location ) { + if ( 'additional' === $location ) { + wc_deprecated_argument( 'location', '8.9.0', 'The "additional" location is deprecated. Use "order" instead.' ); + $location = 'order'; + } + if ( ! $this->is_field( $key ) ) { return new WP_Error( - 'woocommerce_blocks_checkout_field_invalid', + 'woocommerce_invalid_checkout_field', \sprintf( - // translators: % is field key. + // translators: % is field key. __( 'The field %s is invalid.', 'woocommerce' ), $key ) @@ -831,9 +952,9 @@ public function validate_field_for_location( $key, $value, $location ) { if ( ! in_array( $key, $this->fields_locations[ $location ], true ) ) { return new WP_Error( - 'woocommerce_blocks_checkout_field_invalid_location', + 'woocommerce_invalid_checkout_field_location', \sprintf( - // translators: %1$s is field key, %2$s location. + // translators: %1$s is field key, %2$s location. __( 'The field %1$s is invalid for the location %2$s.', 'woocommerce' ), $key, $location @@ -844,9 +965,9 @@ public function validate_field_for_location( $key, $value, $location ) { $field = $this->additional_fields[ $key ]; if ( ! empty( $field['required'] ) && empty( $value ) ) { return new WP_Error( - 'woocommerce_blocks_checkout_field_required', + 'woocommerce_required_checkout_field', \sprintf( - // translators: %s is field key. + // translators: %s is field key. __( 'The field %s is required.', 'woocommerce' ), $key ) @@ -856,6 +977,28 @@ public function validate_field_for_location( $key, $value, $location ) { return true; } + /** + * Returns all fields key for a given group. + * + * @param string $group The group to get the key for (shipping|billing|other). + * + * @return string[] Field keys. + */ + public function get_fields_for_group( $group = 'other' ) { + if ( 'shipping' === $group ) { + return $this->get_fields_for_location( 'address' ); + } + + if ( 'billing' === $group ) { + return $this->get_fields_for_location( 'address' ); + } + + return \array_merge( + $this->get_fields_for_location( 'contact' ), + $this->get_fields_for_location( 'order' ) + ); + } + /** * Returns true if the given key is a valid field. * @@ -867,21 +1010,40 @@ public function is_field( $key ) { return array_key_exists( $key, $this->additional_fields ); } + /** + * Returns true if the given key is a valid customer field. + * + * Customer fields are fields saved to the customer data, like address and contact fields. + * + * @param string $key The field key. + * + * @return bool True if the field is valid, false otherwise. + */ + public function is_customer_field( $key ) { + return in_array( $key, array_intersect( array_merge( $this->get_address_fields_keys(), $this->get_contact_fields_keys() ), array_keys( $this->additional_fields ) ), true ); + } + /** * Persists a field value for a given order. This would also optionally set the field value on the customer object if the order is linked to a registered customer. * * @param string $key The field key. * @param mixed $value The field value. * @param WC_Order $order The order to persist the field for. + * @param string $group The group to persist the field for (shipping|billing|other). * @param bool $set_customer Whether to set the field value on the customer or not. * * @return void */ - public function persist_field_for_order( $key, $value, $order, $set_customer = true ) { - $this->set_array_meta( $key, $value, $order ); + public function persist_field_for_order( string $key, $value, WC_Order $order, string $group = 'other', bool $set_customer = true ) { + if ( 'additional' === $group ) { + wc_deprecated_argument( 'group', '8.9.0', 'The "additional" group is deprecated. Use "other" instead.' ); + $group = 'other'; + } + + $this->set_array_meta( $key, $value, $order, $group ); if ( $set_customer && $order->get_customer_id() ) { $customer = new WC_Customer( $order->get_customer_id() ); - $this->persist_field_for_customer( $key, $value, $customer ); + $this->persist_field_for_customer( $key, $value, $customer, $group ); } } @@ -891,11 +1053,17 @@ public function persist_field_for_order( $key, $value, $order, $set_customer = t * @param string $key The field key. * @param mixed $value The field value. * @param WC_Customer $customer The customer to persist the field for. + * @param string $group The group to persist the field for (shipping|billing|other). * * @return void */ - public function persist_field_for_customer( $key, $value, $customer ) { - $this->set_array_meta( $key, $value, $customer ); + public function persist_field_for_customer( string $key, $value, WC_Customer $customer, string $group = 'other' ) { + if ( 'additional' === $group ) { + wc_deprecated_argument( 'group', '8.9.0', 'The "additional" group is deprecated. Use "other" instead.' ); + $group = 'other'; + } + + $this->set_array_meta( $key, $value, $customer, $group ); } /** @@ -903,228 +1071,183 @@ public function persist_field_for_customer( $key, $value, $customer ) { * * @param string $key The field key. * @param mixed $value The field value. - * @param WC_Customer|WC_Order $object The object to set the field value for. + * @param WC_Customer|WC_Order $wc_object The object to set the field value for. + * @param string $group The group to set the field value for (shipping|billing|other). * * @return void */ - private function set_array_meta( $key, $value, $object ) { - $meta_key = ''; - - if ( 0 === strpos( $key, '/billing/' ) ) { - $meta_key = self::BILLING_FIELDS_KEY; - $key = str_replace( '/billing/', '', $key ); - } elseif ( 0 === strpos( $key, '/shipping/' ) ) { - $meta_key = self::SHIPPING_FIELDS_KEY; - $key = str_replace( '/shipping/', '', $key ); - } else { - $meta_key = self::ADDITIONAL_FIELDS_KEY; - } - - $meta_data = $object->get_meta( $meta_key, true ); - - if ( ! is_array( $meta_data ) ) { - $meta_data = []; + private function set_array_meta( string $key, $value, WC_Data $wc_object, string $group ) { + $meta_key = self::get_group_key( $group ) . $key; + + /** + * Allow reacting for saving an additional field value. + * + * @param string $key The key of the field being saved. + * @param mixed $value The value of the field being saved. + * @param string $group The group of this location (shipping|billing|other). + * @param WC_Customer|WC_Order $wc_object The object to set the field value for. + * + * @since 8.9.0 + */ + do_action( 'woocommerce_set_additional_field_value', $key, $value, $group, $wc_object ); + // Convert boolean values to strings because Data Stores will skip false values. + if ( is_bool( $value ) ) { + $value = $value ? '1' : '0'; } - - $meta_data[ $key ] = $value; - // Replacing all meta using `add_meta_data`. For some reason `update_meta_data` causes duplicate keys. - $object->add_meta_data( $meta_key, $meta_data, true ); - } - - /** - * Returns a field value for a given object. - * - * @param string $key The field key. - * @param WC_Customer $customer The customer to get the field value for. - * @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group. - * - * @return mixed The field value. - */ - public function get_field_from_customer( $key, $customer, $group = '' ) { - return $this->get_field_from_object( $key, $customer, $group ); - } - - /** - * Returns a field value for a given order. - * - * @param string $field The field key. - * @param WC_Order $order The order to get the field value for. - * @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group. - * - * @return mixed The field value. - */ - public function get_field_from_order( $field, $order, $group = '' ) { - return $this->get_field_from_object( $field, $order, $group ); + $wc_object->add_meta_data( $meta_key, $value, true ); } /** * Returns a field value for a given object. * * @param string $key The field key. - * @param WC_Customer|WC_Order $object The customer to get the field value for. - * @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group. + * @param WC_Customer|WC_Order $wc_object The customer or order to get the field value for. + * @param string $group The group to get the field value for (shipping|billing|other). * * @return mixed The field value. */ - private function get_field_from_object( $key, $object, $group = '' ) { - $meta_key = ''; - if ( 0 === strpos( $key, '/billing/' ) || 'billing' === $group ) { - $meta_key = self::BILLING_FIELDS_KEY; - $key = str_replace( '/billing/', '', $key ); - } elseif ( 0 === strpos( $key, '/shipping/' ) || 'shipping' === $group ) { - $meta_key = self::SHIPPING_FIELDS_KEY; - $key = str_replace( '/shipping/', '', $key ); - } else { - $meta_key = self::ADDITIONAL_FIELDS_KEY; + public function get_field_from_object( string $key, WC_Data $wc_object, string $group = 'other' ) { + if ( 'additional' === $group ) { + wc_deprecated_argument( 'group', '8.9.0', 'The "additional" group is deprecated. Use "other" instead.' ); + $group = 'other'; } - $meta_data = $object->get_meta( $meta_key, true ); + $meta_key = self::get_group_key( $group ) . $key; - if ( ! is_array( $meta_data ) ) { - return ''; - } + $value = $wc_object->get_meta( $meta_key, true ); - if ( ! isset( $meta_data[ $key ] ) ) { - return ''; + if ( ! $value ) { + /** + * Allow providing a default value for additional fields if no value is already set. + * + * @param null $value The default value for the filter, always null. + * @param string $group The group of this key (shipping|billing|other). + * @param WC_Data $wc_object The object to get the field value for. + * + * @since 8.9.0 + */ + $value = apply_filters( "woocommerce_get_default_value_for_{$key}", null, $group, $wc_object ); } - return $meta_data[ $key ]; - } - - /** - * Returns an array of all fields values for a given customer. - * - * @param WC_Customer $customer The customer to get the fields for. - * @param bool $all Whether to return all fields or only the ones that are still registered. Default false. - * - * @return array An array of fields. - */ - public function get_all_fields_from_customer( $customer, $all = false ) { - $meta_data = [ - 'billing' => [], - 'shipping' => [], - 'additional' => [], - ]; + // We cast the value to a boolean if the field is a checkbox. + if ( $this->is_field( $key ) && 'checkbox' === $this->additional_fields[ $key ]['type'] ) { + return '1' === $value; + } - if ( $customer instanceof WC_Customer ) { - $meta_data['billing'] = $customer->get_meta( self::BILLING_FIELDS_KEY, true ); - $meta_data['shipping'] = $customer->get_meta( self::SHIPPING_FIELDS_KEY, true ); - $meta_data['additional'] = $customer->get_meta( self::ADDITIONAL_FIELDS_KEY, true ); + if ( null === $value ) { + return ''; } - return $this->format_meta_data( $meta_data, $all ); + + return $value; } /** - * Returns an array of all fields values for a given order. + * Returns an array of all fields values for a given object in a group. * - * @param WC_Order $order The order to get the fields for. - * @param bool $all Whether to return all fields or only the ones that are still registered. Default false. + * @param WC_Data $wc_object The object or order to get the fields for. + * @param string $group The group to get the fields for (shipping|billing|other). + * @param bool $all Whether to return all fields or only the ones that are still registered. Default false. * * @return array An array of fields. */ - public function get_all_fields_from_order( $order, $all = false ) { - $meta_data = [ - 'billing' => [], - 'shipping' => [], - 'additional' => [], - ]; - - if ( $order instanceof WC_Order ) { - $meta_data['billing'] = $order->get_meta( self::BILLING_FIELDS_KEY, true ); - $meta_data['shipping'] = $order->get_meta( self::SHIPPING_FIELDS_KEY, true ); - $meta_data['additional'] = $order->get_meta( self::ADDITIONAL_FIELDS_KEY, true ); + public function get_all_fields_from_object( WC_Data $wc_object, string $group = 'other', bool $all = false ) { + if ( 'additional' === $group ) { + wc_deprecated_argument( 'group', '8.9.0', 'The "additional" group is deprecated. Use "other" instead.' ); + $group = 'other'; } - return $this->format_meta_data( $meta_data, $all ); - } - /** - * Returns an array of all fields values for a given meta object. It would add the billing or shipping prefix to the keys. - * - * @param array $meta The meta data to format. - * @param bool $all Whether to return all fields or only the ones that are still registered. Default false. - * - * @return array An array of fields. - */ - private function format_meta_data( $meta, $all = false ) { - $billing_fields = $meta['billing'] ?? []; - $shipping_fields = $meta['shipping'] ?? []; - $additional_fields = $meta['additional'] ?? []; + $meta_data = []; - $fields = []; + $prefix = self::get_group_key( $group ); - if ( is_array( $billing_fields ) ) { - foreach ( $billing_fields as $key => $value ) { - if ( ! $all && ! $this->is_field( $key ) ) { - continue; + if ( $wc_object instanceof WC_Data ) { + $meta = $wc_object->get_meta_data(); + foreach ( $meta as $meta_data_object ) { + if ( 0 === \strpos( $meta_data_object->key, $prefix ) ) { + $key = \str_replace( $prefix, '', $meta_data_object->key ); + if ( $all || $this->is_field( $key ) ) { + $meta_data[ $key ] = $meta_data_object->value; + } } - $fields[ '/billing/' . $key ] = $value; } } - if ( is_array( $shipping_fields ) ) { - foreach ( $shipping_fields as $key => $value ) { - if ( ! $all && ! $this->is_field( $key ) ) { - continue; - } - $fields[ '/shipping/' . $key ] = $value; + $missing_fields = array_diff( array_keys( $this->get_fields_for_group( $group ) ), array_keys( $meta_data ) ); + + foreach ( $missing_fields as $missing_field ) { + /** + * Allow providing a default value for additional fields if no value is already set. + * + * @param null $value The default value for the filter, always null. + * @param string $group The group of this key (shipping|billing|other). + * @param WC_Data $wc_object The object to get the field value for. + * + * @since 8.9.0 + */ + $value = apply_filters( "woocommerce_get_default_value_for_{$missing_field}", null, $group, $wc_object ); + + if ( $value ) { + $meta_data[ $missing_field ] = $value; } } - if ( is_array( $additional_fields ) ) { - foreach ( $additional_fields as $key => $value ) { - if ( ! $all && ! $this->is_field( $key ) ) { - continue; + return $meta_data; + } + + /** + * Copies additional fields from an order to a customer. + * + * @param WC_Order $order The order to sync the fields for. + * @param WC_Customer $customer The customer to sync the fields for. + */ + public function sync_customer_additional_fields_with_order( WC_Order $order, WC_Customer $customer ) { + foreach ( $this->groups as $group ) { + $order_additional_fields = $this->get_all_fields_from_object( $order, $group, true ); + + // Sync customer additional fields with order additional fields. + foreach ( $order_additional_fields as $key => $value ) { + if ( $this->is_customer_field( $key ) ) { + $this->persist_field_for_customer( $key, $value, $customer, $group ); } - $fields[ $key ] = $value; } } - - return $fields; } /** - * From a set of fields, returns only the ones that should be saved to the customer. - * For now, this only supports fields in address location. + * Copies additional fields from a customer to an order. * - * @param array $fields The fields to filter. - * @return array The filtered fields. + * @param WC_Order $order The order to sync the fields for. + * @param WC_Customer $customer The customer to sync the fields for. */ - public function filter_fields_for_customer( $fields ) { - $customer_fields_keys = array_merge( - $this->get_address_fields_keys(), - $this->get_contact_fields_keys(), - ); - return array_filter( - $fields, - function ( $key ) use ( $customer_fields_keys ) { - if ( 0 === strpos( $key, '/billing/' ) ) { - $key = str_replace( '/billing/', '', $key ); - } elseif ( 0 === strpos( $key, '/shipping/' ) ) { - $key = str_replace( '/shipping/', '', $key ); + public function sync_order_additional_fields_with_customer( WC_Order $order, WC_Customer $customer ) { + foreach ( $this->groups as $group ) { + $customer_additional_fields = $this->get_all_fields_from_object( $customer, $group, true ); + + // Sync order additional fields with customer additional fields. + foreach ( $customer_additional_fields as $key => $value ) { + if ( $this->is_field( $key ) ) { + $this->persist_field_for_order( $key, $value, $order, $group, false ); } - return in_array( $key, $customer_fields_keys, true ); - }, - ARRAY_FILTER_USE_KEY - ); + } + } } - /** * From a set of fields, returns only the ones for a given location. * * @param array $fields The fields to filter. - * @param string $location The location to validate the field for (address|contact|additional). + * @param string $location The location to validate the field for (address|contact|order). * @return array The filtered fields. */ - public function filter_fields_for_location( $fields, $location ) { + public function filter_fields_for_location( array $fields, string $location ) { + if ( 'additional' === $location ) { + wc_deprecated_argument( 'location', '8.9.0', 'The "additional" location is deprecated. Use "order" instead.' ); + $location = 'order'; + } + return array_filter( $fields, function ( $key ) use ( $location ) { - if ( 0 === strpos( $key, '/billing/' ) ) { - $key = str_replace( '/billing/', '', $key ); - } elseif ( 0 === strpos( $key, '/shipping/' ) ) { - $key = str_replace( '/shipping/', '', $key ); - } return $this->is_field( $key ) && $this->get_field_location( $key ) === $location; }, ARRAY_FILTER_USE_KEY @@ -1150,17 +1273,27 @@ function ( $field ) { * Get additional fields for an order. * * @param WC_Order $order Order object. - * @param string $location The location to get fields for (address|contact|additional). - * @param string $group The group to get the field value for (shipping|billing|'') in which '' refers to the additional group. + * @param string $location The location to get fields for (address|contact|order). + * @param string $group The group to get the field value for (shipping|billing|other). * @param string $context The context to get the field value for (edit|view). * @return array An array of fields definitions as well as their values formatted for display. */ - public function get_order_additional_fields_with_values( $order, $location, $group = '', $context = 'edit' ) { + public function get_order_additional_fields_with_values( WC_Order $order, string $location, string $group = 'other', string $context = 'edit' ) { + if ( 'additional' === $location ) { + wc_deprecated_argument( 'location', '8.9.0', 'The "additional" location is deprecated. Use "order" instead.' ); + $location = 'order'; + } + + if ( 'additional' === $group ) { + wc_deprecated_argument( 'group', '8.9.0', 'The "additional" group is deprecated. Use "other" instead.' ); + $group = 'other'; + } + $fields = $this->get_fields_for_location( $location ); $fields_with_values = []; foreach ( $fields as $field_key => $field ) { - $value = $this->get_field_from_order( $field_key, $order, $group ); + $value = $this->get_field_from_object( $field_key, $order, $group ); if ( '' === $value || null === $value ) { continue; @@ -1196,4 +1329,46 @@ public function format_additional_field_value( $value, $field ) { return $value; } + + /** + * Returns a group meta prefix based on its name. + * + * @param string $group_name The group name (billing|shipping|other). + * @return string The group meta prefix. + */ + public static function get_group_key( $group_name ) { + if ( 'additional' === $group_name ) { + wc_deprecated_argument( 'group_name', '8.9.0', 'The "additional" group is deprecated. Use "other" instead.' ); + $group_name = 'other'; + } + + if ( 'billing' === $group_name ) { + return self::BILLING_FIELDS_PREFIX; + } + if ( 'shipping' === $group_name ) { + return self::SHIPPING_FIELDS_PREFIX; + } + return self::OTHER_FIELDS_PREFIX; + } + + /** + * Returns a group name based on passed group key. + * + * @param string $group_key The group name (_wc_billing|_wc_shipping|_wc_other). + * @return string The group meta prefix. + */ + public static function get_group_name( $group_key ) { + if ( '_wc_additional' === $group_key ) { + wc_deprecated_argument( 'group_key', '8.9.0', 'The "_wc_additional" group key is deprecated. Use "_wc_other" instead.' ); + $group_key = '_wc_other'; + } + + if ( 0 === \strpos( self::BILLING_FIELDS_PREFIX, $group_key ) ) { + return 'billing'; + } + if ( 0 === \strpos( self::SHIPPING_FIELDS_PREFIX, $group_key ) ) { + return 'shipping'; + } + return 'other'; + } } diff --git a/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFieldsAdmin.php b/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFieldsAdmin.php index 2f637dc230f12..ea400ea01fc1a 100644 --- a/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFieldsAdmin.php +++ b/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFieldsAdmin.php @@ -32,7 +32,7 @@ public function init() { add_filter( 'woocommerce_admin_billing_fields', array( $this, 'admin_address_fields' ), 10, 3 ); add_filter( 'woocommerce_admin_billing_fields', array( $this, 'admin_contact_fields' ), 10, 3 ); add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_address_fields' ), 10, 3 ); - add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_additional_fields' ), 10, 3 ); + add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_order_fields' ), 10, 3 ); } /** @@ -73,7 +73,9 @@ protected function format_field_for_meta_box( $field, $key ) { * @param \WC_Order $order The order to update the field for. */ public function update_callback( $key, $value, $order ) { - $this->checkout_fields_controller->persist_field_for_order( $key, $value, $order, false ); + list( $group, $key ) = explode( '/', $key, 2 ); + $group = CheckoutFields::get_group_name( $group ); + $this->checkout_fields_controller->persist_field_for_order( $key, $value, $order, $group, false ); } /** @@ -89,11 +91,11 @@ public function admin_address_fields( $fields, $order = null, $context = 'edit' return $fields; } - $group = doing_action( 'woocommerce_admin_billing_fields' ) ? 'billing' : 'shipping'; - $additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $group, $context ); + $group_name = doing_action( 'woocommerce_admin_billing_fields' ) ? 'billing' : 'shipping'; + $additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $group_name, $context ); foreach ( $additional_fields as $key => $field ) { - $group_key = '/' . $group . '/' . $key; - $additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $group_key ); + $prefixed_key = CheckoutFields::get_group_key( $group_name ) . $key; + $additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key ); } array_splice( @@ -123,16 +125,14 @@ public function admin_contact_fields( $fields, $order = null, $context = 'edit' return $fields; } - $additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', '', $context ); + $additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'other', $context ); - return array_merge( - $fields, - array_map( - array( $this, 'format_field_for_meta_box' ), - $additional_fields, - array_keys( $additional_fields ) - ) - ); + foreach ( $additional_fields as $key => $field ) { + $prefixed_key = CheckoutFields::get_group_key( 'other' ) . $key; + $additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key ); + } + + return array_merge( $fields, $additional_fields ); } /** @@ -143,20 +143,18 @@ public function admin_contact_fields( $fields, $order = null, $context = 'edit' * @param string $context The context to show the fields for. * @return array */ - public function admin_additional_fields( $fields, $order = null, $context = 'edit' ) { + public function admin_order_fields( $fields, $order = null, $context = 'edit' ) { if ( ! $order instanceof \WC_Order ) { return $fields; } - $additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'additional', '', $context ); + $additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'order', 'other', $context ); - return array_merge( - $fields, - array_map( - array( $this, 'format_field_for_meta_box' ), - $additional_fields, - array_keys( $additional_fields ) - ) - ); + foreach ( $additional_fields as $key => $field ) { + $prefixed_key = CheckoutFields::get_group_key( 'other' ) . $key; + $additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key ); + } + + return array_merge( $fields, $additional_fields ); } } diff --git a/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFieldsFrontend.php b/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFieldsFrontend.php index d0bd36ec6ee86..bedc10d9a20b4 100644 --- a/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFieldsFrontend.php +++ b/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFieldsFrontend.php @@ -32,7 +32,7 @@ public function __construct( CheckoutFields $checkout_fields_controller ) { public function init() { // Show custom checkout fields on the order details page. add_action( 'woocommerce_order_details_after_customer_address', array( $this, 'render_order_address_fields' ), 10, 2 ); - add_action( 'woocommerce_order_details_after_customer_details', array( $this, 'render_order_additional_fields' ), 10 ); + add_action( 'woocommerce_order_details_after_customer_details', array( $this, 'render_order_other_fields' ), 10 ); // Show custom checkout fields on the My Account page. add_action( 'woocommerce_my_account_after_my_address', array( $this, 'render_address_fields' ), 10, 1 ); @@ -87,10 +87,10 @@ public function render_order_address_fields( $address_type, $order ) { * * @param WC_Order $order Order object. */ - public function render_order_additional_fields( $order ) { + public function render_order_other_fields( $order ) { $fields = array_merge( - $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', '', 'view' ), - $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'additional', '', 'view' ), + $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'other', 'view' ), + $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'order', 'other', 'view' ), ); if ( ! $fields ) { @@ -122,7 +122,7 @@ public function render_address_fields( $address_type ) { foreach ( $fields as $key => $field ) { $value = $this->checkout_fields_controller->format_additional_field_value( - $this->checkout_fields_controller->get_field_from_customer( $key, $customer, $address_type ), + $this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type ), $field ); @@ -160,8 +160,10 @@ public function edit_account_form_fields() { $fields = $this->checkout_fields_controller->get_fields_for_location( 'contact' ); foreach ( $fields as $key => $field ) { + $field_key = CheckoutFields::get_group_key( 'other' ) . $key; $form_field = $field; - $form_field['value'] = $this->checkout_fields_controller->get_field_from_customer( $key, $customer, 'contact' ); + $form_field['id'] = $field_key; + $form_field['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, 'contact' ); if ( 'select' === $field['type'] ) { $form_field['options'] = array_column( $field['options'], 'label', 'value' ); @@ -189,12 +191,13 @@ public function save_account_form_fields( $user_id ) { $additional_fields = $this->checkout_fields_controller->get_fields_for_location( 'contact' ); $field_values = array(); - foreach ( $additional_fields as $key => $field ) { - if ( ! isset( $_POST[ $key ] ) ) { + foreach ( array_keys( $additional_fields ) as $key ) { + $post_key = CheckoutFields::get_group_key( 'other' ) . $key; + if ( ! isset( $_POST[ $post_key ] ) ) { continue; } - $field_value = $this->checkout_fields_controller->sanitize_field( $key, wc_clean( wp_unslash( $_POST[ $key ] ) ) ); + $field_value = $this->checkout_fields_controller->sanitize_field( $key, wc_clean( wp_unslash( $_POST[ $post_key ] ) ) ); $validation = $this->checkout_fields_controller->validate_field( $key, $field_value ); if ( is_wp_error( $validation ) && $validation->has_errors() ) { @@ -207,11 +210,11 @@ public function save_account_form_fields( $user_id ) { // Persist individual additional fields to customer. foreach ( $field_values as $key => $value ) { - $this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer ); + $this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer, 'other' ); } // Validate all fields for this location. - $location_validation = $this->checkout_fields_controller->validate_fields_for_location( $field_values, 'contact' ); + $location_validation = $this->checkout_fields_controller->validate_fields_for_location( $field_values, 'contact', 'other' ); if ( is_wp_error( $location_validation ) && $location_validation->has_errors() ) { wc_add_notice( $location_validation->get_error_message(), 'error' ); @@ -233,9 +236,9 @@ public function edit_address_fields( $address, $address_type ) { $fields = $this->checkout_fields_controller->get_fields_for_location( 'address' ); foreach ( $fields as $key => $field ) { - $field_key = "/{$address_type}/{$key}"; + $field_key = CheckoutFields::get_group_key( $address_type ) . $key; $address[ $field_key ] = $field; - $address[ $field_key ]['value'] = $this->checkout_fields_controller->get_field_from_customer( $key, $customer, $address_type ); + $address[ $field_key ]['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type ); if ( 'select' === $field['type'] ) { $address[ $field_key ]['options'] = array_column( $field['options'], 'label', 'value' ); @@ -266,8 +269,8 @@ public function save_address_fields( $user_id, $address_type, $address, $custome $additional_fields = $this->checkout_fields_controller->get_fields_for_location( 'address' ); $field_values = array(); - foreach ( $additional_fields as $key => $field ) { - $post_key = "/{$address_type}/{$key}"; + foreach ( array_keys( $additional_fields ) as $key ) { + $post_key = CheckoutFields::get_group_key( $address_type ) . $key; if ( ! isset( $_POST[ $post_key ] ) ) { continue; @@ -286,7 +289,7 @@ public function save_address_fields( $user_id, $address_type, $address, $custome // Persist individual additional fields to customer. foreach ( $field_values as $key => $value ) { - $this->checkout_fields_controller->persist_field_for_customer( "/{$address_type}/{$key}", $value, $customer ); + $this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer, $address_type ); } // Validate all fields for this location. diff --git a/plugins/woocommerce/src/Blocks/Domain/Services/functions.php b/plugins/woocommerce/src/Blocks/Domain/Services/functions.php index ee667a6509cb2..41485f3e19842 100644 --- a/plugins/woocommerce/src/Blocks/Domain/Services/functions.php +++ b/plugins/woocommerce/src/Blocks/Domain/Services/functions.php @@ -3,14 +3,14 @@ use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields; -if ( ! function_exists( '__experimental_woocommerce_blocks_register_checkout_field' ) ) { +if ( ! function_exists( 'woocommerce_register_additional_checkout_field' ) ) { /** * Register a checkout field. * * @param array $options Field arguments. See CheckoutFields::register_checkout_field() for details. * @throws \Exception If field registration fails. */ - function __experimental_woocommerce_blocks_register_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore + function woocommerce_register_additional_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore // Check if `woocommerce_blocks_loaded` ran. If not then the CheckoutFields class will not be available yet. // In that case, re-hook `woocommerce_blocks_loaded` and try running this again. @@ -19,7 +19,7 @@ function __experimental_woocommerce_blocks_register_checkout_field( $options ) { add_action( 'woocommerce_blocks_loaded', function () use ( $options ) { - __experimental_woocommerce_blocks_register_checkout_field( $options ); + woocommerce_register_additional_checkout_field( $options ); } ); return; @@ -32,6 +32,20 @@ function () use ( $options ) { } } +if ( ! function_exists( '__experimental_woocommerce_blocks_register_checkout_field' ) ) { + + /** + * Register a checkout field. + * + * @param array $options Field arguments. See CheckoutFields::register_checkout_field() for details. + * @throws \Exception If field registration fails. + * @deprecated 5.6.0 Use woocommerce_register_additional_checkout_field() instead. + */ + function __experimental_woocommerce_blocks_register_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore + wc_deprecated_function( __FUNCTION__, '8.9.0', 'woocommerce_register_additional_checkout_field' ); + woocommerce_register_additional_checkout_field( $options ); + } +} if ( ! function_exists( '__internal_woocommerce_blocks_deregister_checkout_field' ) ) { /** diff --git a/plugins/woocommerce/src/Blocks/Shipping/ShippingController.php b/plugins/woocommerce/src/Blocks/Shipping/ShippingController.php index 66b324c7fa9f5..219ca8abe4c18 100644 --- a/plugins/woocommerce/src/Blocks/Shipping/ShippingController.php +++ b/plugins/woocommerce/src/Blocks/Shipping/ShippingController.php @@ -459,7 +459,7 @@ public function track_local_pickup( $served, $result, $request ) { $data = array( 'local_pickup_enabled' => 'yes' === $settings['enabled'] ? true : false, - 'title' => __( 'Local Pickup', 'woocommerce' ) === $settings['title'], + 'title' => __( 'Pickup', 'woocommerce' ) === $settings['title'], 'price' => '' === $settings['cost'] ? true : false, 'cost' => '' === $settings['cost'] ? 0 : $settings['cost'], 'taxes' => $settings['tax_status'], diff --git a/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php b/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php index abf30f400a9dc..3d622a345c23f 100644 --- a/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php +++ b/plugins/woocommerce/src/Blocks/Templates/AbstractTemplateCompatibility.php @@ -29,13 +29,13 @@ public function init() { add_filter( 'render_block_data', - function( $parsed_block, $source_block, $parent_block ) { + function ( $parsed_block, $source_block, $parent_block ) { /** * Filter to disable the compatibility layer for the blockified templates. * * This hook allows to disable the compatibility layer for the blockified templates. * - * @since TBD + * @since 7.6.0 * @param boolean. */ $is_disabled_compatility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false ); @@ -45,7 +45,6 @@ function( $parsed_block, $source_block, $parent_block ) { } return $this->update_render_block_data( $parsed_block, $source_block, $parent_block ); - }, 10, 3 @@ -59,7 +58,7 @@ function ( $block_content, $block ) { * * This hook allows to disable the compatibility layer for the blockified. * - * @since TBD + * @since 7.6.0 * @param boolean. */ $is_disabled_compatility_layer = apply_filters( 'woocommerce_disable_compatibility_layer', false ); diff --git a/plugins/woocommerce/src/Blocks/Templates/ArchiveProductTemplatesCompatibility.php b/plugins/woocommerce/src/Blocks/Templates/ArchiveProductTemplatesCompatibility.php index cfbd2c447a160..74f3615558237 100644 --- a/plugins/woocommerce/src/Blocks/Templates/ArchiveProductTemplatesCompatibility.php +++ b/plugins/woocommerce/src/Blocks/Templates/ArchiveProductTemplatesCompatibility.php @@ -78,7 +78,7 @@ public function inject_hooks( $block_content, $block ) { $block_hooks = array_filter( $this->hook_data, - function( $hook ) use ( $block_name ) { + function ( $hook ) use ( $block_name ) { return in_array( $block_name, $hook['block_names'], true ); } ); @@ -98,9 +98,9 @@ function( $hook ) use ( $block_name ) { } $supported_blocks = array_merge( - [], + array(), ...array_map( - function( $hook ) { + function ( $hook ) { return $hook['block_names']; }, array_values( $this->hook_data ) @@ -312,7 +312,7 @@ private function restore_default_hooks() { continue; } foreach ( $data['hooked'] as $callback => $priority ) { - if ( ! in_array( $callback, $data['permanently_removed_actions'] ?? [], true ) ) { + if ( ! in_array( $callback, $data['permanently_removed_actions'] ?? array(), true ) ) { add_action( $hook, $callback, $priority ); } } diff --git a/plugins/woocommerce/src/Blocks/Templates/CartTemplate.php b/plugins/woocommerce/src/Blocks/Templates/CartTemplate.php index c6045807cefb2..8c72222bae93d 100644 --- a/plugins/woocommerce/src/Blocks/Templates/CartTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/CartTemplate.php @@ -1,8 +1,6 @@ get_placeholder_page(); - return null !== $placeholder && $post instanceof \WP_Post && $placeholder->post_name === $post->post_name; - } -} diff --git a/plugins/woocommerce/src/Blocks/Templates/ComingSoonStoreOnlyTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ComingSoonTemplate.php similarity index 52% rename from plugins/woocommerce/src/Blocks/Templates/ComingSoonStoreOnlyTemplate.php rename to plugins/woocommerce/src/Blocks/Templates/ComingSoonTemplate.php index a45beb62e34f2..de791dce4c7f9 100644 --- a/plugins/woocommerce/src/Blocks/Templates/ComingSoonStoreOnlyTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/ComingSoonTemplate.php @@ -2,18 +2,18 @@ namespace Automattic\WooCommerce\Blocks\Templates; /** - * ComingSoonStoreOnlyTemplate class. + * ComingSoonTemplate class. * * @internal */ -class ComingSoonStoreOnlyTemplate extends AbstractPageTemplate { +class ComingSoonTemplate extends AbstractPageTemplate { /** * The slug of the template. * * @var string */ - const SLUG = 'page-coming-soon-store-only'; + const SLUG = 'coming-soon'; /** * Returns the title of the template. @@ -21,7 +21,7 @@ class ComingSoonStoreOnlyTemplate extends AbstractPageTemplate { * @return string */ public function get_template_title() { - return _x( 'Page: Coming soon store only', 'Template name', 'woocommerce' ); + return _x( 'Page: Coming soon', 'Template name', 'woocommerce' ); } /** @@ -30,7 +30,7 @@ public function get_template_title() { * @return string */ public function get_template_description() { - return __( 'Page template for Coming soon page when access is restricted for store pages.', 'woocommerce' ); + return __( 'Page template for Coming soon page.', 'woocommerce' ); } /** @@ -39,8 +39,7 @@ public function get_template_description() { * @return \WP_Post|null Post object or null. */ protected function get_placeholder_page() { - $page_id = get_option( 'woocommerce_coming_soon_page_id' ); - return $page_id ? get_post( $page_id ) : null; + return null; } /** @@ -49,8 +48,6 @@ protected function get_placeholder_page() { * @return boolean */ protected function is_active_template() { - global $post; - $placeholder = $this->get_placeholder_page(); - return null !== $placeholder && $post instanceof \WP_Post && $placeholder->post_name === $post->post_name; + return false; } } diff --git a/plugins/woocommerce/src/Blocks/Templates/MiniCartTemplate.php b/plugins/woocommerce/src/Blocks/Templates/MiniCartTemplate.php index 92c319892e027..9fde10378c10d 100644 --- a/plugins/woocommerce/src/Blocks/Templates/MiniCartTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/MiniCartTemplate.php @@ -54,13 +54,13 @@ public function get_template_description() { * @return array The supported template part areas including the Mini-Cart one. */ public function register_mini_cart_template_part_area( $default_area_definitions ) { - $mini_cart_template_part_area = [ + $mini_cart_template_part_area = array( 'area' => 'mini-cart', 'label' => __( 'Mini-Cart', 'woocommerce' ), 'description' => __( 'The Mini-Cart template allows shoppers to see their cart items and provides access to the Cart and Checkout pages.', 'woocommerce' ), 'icon' => 'mini-cart', 'area_tag' => 'mini-cart', - ]; - return array_merge( $default_area_definitions, [ $mini_cart_template_part_area ] ); + ); + return array_merge( $default_area_definitions, array( $mini_cart_template_part_area ) ); } } diff --git a/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php b/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php index 95c28f1aae133..2f3d3383ccbc3 100644 --- a/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/ProductCatalogTemplate.php @@ -23,7 +23,7 @@ class ProductCatalogTemplate extends AbstractTemplate { */ public function init() { add_action( 'template_redirect', array( $this, 'render_block_template' ) ); - add_filter( 'post_type_archive_title', array( $this, 'update_product_archive_title' ), 10, 2 ); + add_filter( 'current_theme_supports-block-templates', array( $this, 'remove_block_template_support_for_shop_page' ) ); } /** @@ -60,22 +60,28 @@ public function render_block_template() { } /** - * Update the product archive title to "Shop". + * Remove the template panel from the Sidebar of the Shop page because + * the Site Editor handles it. * - * @param string $post_type_name Post type 'name' label. - * @param string $post_type Post type. + * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/6278 * - * @return string + * @param bool $is_support Whether the active theme supports block templates. + * + * @return bool */ - public function update_product_archive_title( $post_type_name, $post_type ) { + public function remove_block_template_support_for_shop_page( $is_support ) { + global $pagenow, $post; + if ( - function_exists( 'is_shop' ) && - is_shop() && - 'product' === $post_type + is_admin() && + 'post.php' === $pagenow && + function_exists( 'wc_get_page_id' ) && + is_a( $post, 'WP_Post' ) && + wc_get_page_id( 'shop' ) === $post->ID ) { - return __( 'Shop', 'woocommerce' ); + return false; } - return $post_type_name; + return $is_support; } } diff --git a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php index 6cffa824a2924..942f807422ff3 100644 --- a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php +++ b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php @@ -5,7 +5,7 @@ use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; /** - * SingleProductTemplae class. + * SingleProductTemplate class. * * @internal */ @@ -51,7 +51,7 @@ public function render_block_template() { if ( ! is_embed() && is_singular( 'product' ) ) { global $post; - $valid_slugs = [ self::SLUG ]; + $valid_slugs = array( self::SLUG ); if ( 'product' === $post->post_type && $post->post_name ) { $valid_slugs[] = 'single-product-' . $post->post_name; } @@ -75,7 +75,7 @@ public function render_block_template() { */ public function update_single_product_content( $query_result, $query, $template_type ) { $query_result = array_map( - function( $template ) { + function ( $template ) { if ( str_contains( $template->slug, self::SLUG ) ) { // We don't want to add the compatibility layer on the Editor Side. // The second condition is necessary to not apply the compatibility layer on the REST API. Gutenberg uses the REST API to clone the template. @@ -84,7 +84,7 @@ function( $template ) { // Add the product class to the body. We should move this to a more appropriate place. add_filter( 'body_class', - function( $classes ) { + function ( $classes ) { return array_merge( $classes, wc_get_product_class() ); } ); @@ -125,7 +125,7 @@ private static function replace_first_single_product_template_block_with_passwor $single_product_template_blocks = array( 'woocommerce/product-image-gallery', 'woocommerce/product-details', 'woocommerce/add-to-cart-form', 'woocommerce/product-meta', 'woocommerce/product-rating', 'woocommerce/product-price', 'woocommerce/related-products' ); return array_reduce( $parsed_blocks, - function( $carry, $block ) use ( $single_product_template_blocks ) { + function ( $carry, $block ) use ( $single_product_template_blocks ) { if ( in_array( $block['blockName'], $single_product_template_blocks, true ) ) { if ( $carry['is_already_replaced'] ) { return array( diff --git a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplateCompatibility.php b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplateCompatibility.php index 7274985ba9cc3..a76f974d32b8d 100644 --- a/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplateCompatibility.php +++ b/plugins/woocommerce/src/Blocks/Templates/SingleProductTemplateCompatibility.php @@ -31,7 +31,7 @@ public function inject_hooks( $block_content, $block ) { $block_hooks = array_filter( $this->hook_data, - function( $hook ) use ( $block_name ) { + function ( $hook ) use ( $block_name ) { return in_array( $block_name, $hook['block_names'], true ); } ); @@ -279,7 +279,7 @@ private static function wrap_single_product_template( $template_content ) { $grouped_blocks = self::group_blocks( $parsed_blocks ); $wrapped_blocks = array_map( - function( $blocks ) { + function ( $blocks ) { if ( 'core/template-part' === $blocks[0]['blockName'] ) { return $blocks; } @@ -306,7 +306,7 @@ function( $blocks ) { private static function inject_custom_attributes_to_first_and_last_block_single_product_template( $wrapped_blocks ) { $template_with_custom_attributes = array_reduce( $wrapped_blocks, - function( $carry, $item ) { + function ( $carry, $item ) { $index = $carry['index']; $carry['index'] = $carry['index'] + 1; @@ -379,7 +379,6 @@ private static function create_wrap_block_group( $blocks ) { $new_block['innerBlocks'] = $blocks; return $new_block; - } /** @@ -421,7 +420,7 @@ private static function has_single_product_template_blocks( $parsed_blocks ) { private static function group_blocks( $parsed_blocks ) { return array_reduce( $parsed_blocks, - function( array $carry, array $block ) { + function ( array $carry, array $block ) { if ( 'core/template-part' === $block['blockName'] ) { $carry[] = array( $block ); return $carry; @@ -480,7 +479,7 @@ private static function is_custom_html( $block ) { private static function serialize_blocks( $parsed_blocks ) { return array_reduce( $parsed_blocks, - function( $carry, $item ) { + function ( $carry, $item ) { if ( is_array( $item ) ) { return $carry . serialize_blocks( $item ); } diff --git a/plugins/woocommerce/src/Blocks/Utils/BlockHooksTrait.php b/plugins/woocommerce/src/Blocks/Utils/BlockHooksTrait.php index 0c5af0c053419..4ac4282378ce0 100644 --- a/plugins/woocommerce/src/Blocks/Utils/BlockHooksTrait.php +++ b/plugins/woocommerce/src/Blocks/Utils/BlockHooksTrait.php @@ -32,7 +32,16 @@ public function register_hooked_block( $hooked_blocks, $position, $anchor_block, $active_theme_name = wp_get_theme()->get( 'Name' ); } - if ( $context ) { + /** + * A list of theme slugs to execute this with. This is a temporary + * measure until improvements to the Block Hooks API allow for exposing + * to all block themes. + * + * @since 8.4.0 + */ + $theme_include_list = apply_filters( 'woocommerce_hooked_blocks_theme_include_list', array( 'Twenty Twenty-Four', 'Twenty Twenty-Three', 'Twenty Twenty-Two', 'Tsubaki', 'Zaino', 'Thriving Artist', 'Amulet', 'Tazza' ) ); + + if ( $context && in_array( $active_theme_name, $theme_include_list, true ) ) { foreach ( $this->hooked_block_placements as $placement ) { if ( $placement['position'] === $position && $placement['anchor'] === $anchor_block ) { diff --git a/plugins/woocommerce/src/Blocks/Utils/BlockTemplateUtils.php b/plugins/woocommerce/src/Blocks/Utils/BlockTemplateUtils.php index 1b1188d1317f5..841ccb7ec50ae 100644 --- a/plugins/woocommerce/src/Blocks/Utils/BlockTemplateUtils.php +++ b/plugins/woocommerce/src/Blocks/Utils/BlockTemplateUtils.php @@ -1,6 +1,7 @@ title ) && $template->title !== $template->slug; + public static function template_has_legacy_template_block( $template ) { + return has_block( 'woocommerce/legacy-template', $template->content ); } /** - * Returns whether the passed `$template` has the legacy template block. - * - * @param object $template The template object. - * @return boolean + * Updates the title, description and area of a template to the correct values and to make them more user-friendly. + * For example, instead of: + * - Title: `Tag (product_tag)` + * - Description: `Displays taxonomy: Tag.` + * we display: + * - Title: `Products by Tag` + * - Description: `Displays products filtered by a tag.`. + * + * @param WP_Block_Template $template The template object. + * @param string $template_type wp_template or wp_template_part. + * + * @return WP_Block_Template */ - public static function template_has_legacy_template_block( $template ) { - return has_block( 'woocommerce/legacy-template', $template->content ); + public static function update_template_data( $template, $template_type ) { + if ( ! $template ) { + return $template; + } + if ( empty( $template->title ) || $template->title === $template->slug ) { + $template->title = self::get_block_template_title( $template->slug ); + } + if ( empty( $template->description ) ) { + $template->description = self::get_block_template_description( $template->slug ); + } + if ( empty( $template->area ) || 'uncategorized' === $template->area ) { + $template->area = self::get_block_template_area( $template->slug, $template_type ); + } + + return $template; } /** diff --git a/plugins/woocommerce/src/Container.php b/plugins/woocommerce/src/Container.php index 2c0258921af87..8a1d5d9fbf3fc 100644 --- a/plugins/woocommerce/src/Container.php +++ b/plugins/woocommerce/src/Container.php @@ -30,6 +30,7 @@ use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\UtilsClassesServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\BatchProcessingServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\LayoutTemplatesServiceProvider; +use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ComingSoonServiceProvider; /** * PSR11 compliant dependency injection container for WooCommerce. @@ -79,6 +80,7 @@ final class Container { LayoutTemplatesServiceProvider::class, LoggingServiceProvider::class, EnginesServiceProvider::class, + ComingSoonServiceProvider::class, ); /** diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php index 5ba6cca0b4f6a..27f00cfedc93e 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/CLIRunner.php @@ -58,16 +58,26 @@ final public function init( CustomOrdersTableController $controller, DataSynchro * Registers commands for CLI. */ public function register_commands() { - WP_CLI::add_command( 'wc cot count_unmigrated', array( $this, 'count_unmigrated' ) ); - WP_CLI::add_command( 'wc cot migrate', array( $this, 'migrate' ) ); - WP_CLI::add_command( 'wc cot sync', array( $this, 'sync' ) ); - WP_CLI::add_command( 'wc cot verify_cot_data', array( $this, 'verify_cot_data' ) ); - WP_CLI::add_command( 'wc cot enable', array( $this, 'enable' ) ); - WP_CLI::add_command( 'wc cot disable', array( $this, 'disable' ) ); + $legacy_commands = array( 'count_unmigrated', 'sync', 'verify_cot_data', 'enable', 'disable' ); + foreach ( $legacy_commands as $cmd ) { + $new_cmd_name = 'verify_cot_data' === $cmd ? 'verify_data' : $cmd; + + WP_CLI::add_command( "wc hpos {$new_cmd_name}", array( $this, $cmd ) ); + WP_CLI::add_command( + "wc cot {$cmd}", + function ( array $args = array(), array $assoc_args = array() ) use ( $cmd, $new_cmd_name ) { + WP_CLI::warning( "Command `wc cot {$cmd}` is deprecated since 8.9.0. Please use `wc hpos {$new_cmd_name}` instead." ); + return call_user_func( array( $this, $cmd ), $args, $assoc_args ); + } + ); + } + WP_CLI::add_command( 'wc hpos cleanup', array( $this, 'cleanup_post_data' ) ); WP_CLI::add_command( 'wc hpos status', array( $this, 'status' ) ); WP_CLI::add_command( 'wc hpos diff', array( $this, 'diff' ) ); WP_CLI::add_command( 'wc hpos backfill', array( $this, 'backfill' ) ); + + WP_CLI::add_command( 'wc cot migrate', array( $this, 'migrate' ) ); // Fully deprecated. No longer works. } /** @@ -77,7 +87,7 @@ public function register_commands() { * * @return bool Whether the COT feature is enabled. */ - private function is_enabled( $log = true ) : bool { + private function is_enabled( $log = true ): bool { if ( ! $this->controller->custom_orders_table_usage_is_enabled() ) { if ( $log ) { WP_CLI::log( @@ -98,7 +108,7 @@ private function is_enabled( $log = true ) : bool { * * ## EXAMPLES * - * wp wc cot count_unmigrated + * wp wc hpos count_unmigrated * * @param array $args Positional arguments passed to the command. * @@ -106,7 +116,7 @@ private function is_enabled( $log = true ) : bool { * * @return int The number of orders to be migrated.* */ - public function count_unmigrated( $args = array(), $assoc_args = array() ) : int { + public function count_unmigrated( $args = array(), $assoc_args = array() ): int { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $order_count = $this->synchronizer->get_current_orders_pending_sync_count(); @@ -147,7 +157,7 @@ public function count_unmigrated( $args = array(), $assoc_args = array() ) : int * * ## EXAMPLES * - * wp wc cot sync --batch-size=500 + * wp wc hpos sync --batch-size=500 * * @param array $args Positional arguments passed to the command. * @param array $assoc_args Associative arguments (options) passed to the command. @@ -212,7 +222,7 @@ public function sync( $args = array(), $assoc_args = array() ) { ) ); - $batch_count ++; + ++$batch_count; $total_time += $batch_total_time; $progress->tick(); @@ -252,7 +262,7 @@ public function sync( $args = array(), $assoc_args = array() ) { } /** - * [Deprecated] Use `wp wc cot sync` instead. + * [Deprecated] Use `wp wc hpos sync` instead. * Copy order data into the postmeta table. * * Note that this could dramatically increase the size of your postmeta table, but is recommended @@ -274,7 +284,7 @@ public function sync( $args = array(), $assoc_args = array() ) { * @param array $args Positional arguments passed to the command. * @param array $assoc_args Associative arguments (options) passed to the command. */ - public function migrate( $args = array(), $assoc_args = array() ) { + public function migrate( array $args = array(), array $assoc_args = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- for backwards compat. WP_CLI::log( __( 'Migrate command is deprecated. Please use `sync` instead.', 'woocommerce' ) ); } @@ -319,7 +329,7 @@ public function migrate( $args = array(), $assoc_args = array() ) { * ## EXAMPLES * * # Verify migrated order data, 500 orders at a time. - * wp wc cot verify_cot_data --batch-size=500 --start-from=0 --end-at=10000 + * wp wc hpos verify_cot_data --batch-size=500 --start-from=0 --end-at=10000 * * @param array $args Positional arguments passed to the command. * @param array $assoc_args Associative arguments (options) passed to the command. @@ -414,7 +424,7 @@ public function verify_cot_data( $args = array(), $assoc_args = array() ) { $error_processing = $error_processing || ! empty( $failed_ids_in_current_batch ); $processed += count( $order_ids ); $batch_total_time = microtime( true ) - $batch_start_time; - $batch_count ++; + ++$batch_count; $total_time += $batch_total_time; if ( count( $failed_ids_in_current_batch ) > 0 ) { @@ -467,7 +477,7 @@ public function verify_cot_data( $args = array(), $assoc_args = array() ) { } else { array_walk( $errors_in_remigrate_batch, - function( &$errors_for_order ) { + function ( &$errors_for_order ) { $errors_for_order[] = array( 'remigrate_failed' => true ); } ); @@ -559,7 +569,7 @@ function( &$errors_for_order ) { * * @return int Order count. */ - private function get_verify_order_count( int $order_id_start, int $order_id_end, array $order_types, bool $log = true ) : int { + private function get_verify_order_count( int $order_id_start, int $order_id_end, array $order_types, bool $log = true ): int { global $wpdb; $order_types_placeholder = implode( ',', array_fill( 0, count( $order_types ), '%s' ) ); @@ -605,7 +615,7 @@ private function get_verify_order_count( int $order_id_start, int $order_id_end, * * @return array Failed IDs with meta details. */ - private function verify_meta_data( array $order_ids, array $failed_ids ) : array { + private function verify_meta_data( array $order_ids, array $failed_ids ): array { $meta_keys_to_ignore = $this->synchronizer->get_ignored_order_props(); global $wpdb; @@ -685,7 +695,7 @@ private function verify_meta_data( array $order_ids, array $failed_ids ) : array * * @return array Normalized data. */ - private function normalize_raw_meta_data( array $data ) : array { + private function normalize_raw_meta_data( array $data ): array { $clubbed_data = array(); foreach ( $data as $row ) { if ( ! isset( $clubbed_data[ $row['entity_id'] ] ) ) { @@ -719,7 +729,7 @@ private function normalize_raw_meta_data( array $data ) : array { * ### EXAMPLES * * # Enable HPOS on new shops. - * wp wc cot enable --for-new-shop + * wp wc hpos enable --for-new-shop * * @param array $args Positional arguments passed to the command. * @param array $assoc_args Associative arguments (options) passed to the command. @@ -778,7 +788,7 @@ public function enable( array $args = array(), array $assoc_args = array() ) { sprintf( // translators: %s is the command to run (wp wc cot sync). __( '[Failed] There are orders pending sync. Please run `%s` to sync pending orders.', 'woocommerce' ), - 'wp wc cot sync', + 'wp wc hpos sync', ) ); $enable_hpos = false; @@ -826,7 +836,7 @@ public function enable( array $args = array(), array $assoc_args = array() ) { * ### EXAMPLES * * # Disable HPOS. - * wp wc cot disable + * wp wc hpos disable * * @param array $args Positional arguments passed to the command. * @param array $assoc_args Associative arguments (options) passed to the command. @@ -849,7 +859,7 @@ public function disable( $args, $assoc_args ) { sprintf( // translators: %s is the command to run (wp wc cot sync). __( '[Failed] There are orders pending sync. Please run `%s` to sync pending orders.', 'woocommerce' ), - 'wp wc cot sync', + 'wp wc hpos sync', ) ); } @@ -958,7 +968,7 @@ public function cleanup_post_data( array $args = array(), array $assoc_args = ar foreach ( $order_ids as $order_id ) { try { $handler->cleanup_post_data( $order_id, $force ); - $count++; + ++$count; // translators: %d is an order ID. WP_CLI::debug( sprintf( __( 'Cleanup completed for order %d.', 'woocommerce' ), $order_id ) ); @@ -1012,7 +1022,7 @@ public function cleanup_post_data( array $args = array(), array $assoc_args = ar * @param array $args Positional arguments passed to the command. * @param array $assoc_args Associative arguments (options) passed to the command. */ - public function status( array $args = array(), array $assoc_args = array() ) { + public function status( array $args = array(), array $assoc_args = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- for backwards compat. $legacy_handler = wc_get_container()->get( LegacyDataHandler::class ); // translators: %s is either 'yes' or 'no'. @@ -1084,7 +1094,7 @@ public function diff( array $args = array(), array $assoc_args = array() ) { // Format the diff array. $diff = array_map( - function( $key, $hpos_value, $cpt_value ) { + function ( $key, $hpos_value, $cpt_value ) { // Format for dates. $hpos_value = is_a( $hpos_value, \WC_DateTime::class ) ? $hpos_value->format( DATE_ATOM ) : $hpos_value; $cpt_value = is_a( $cpt_value, \WC_DateTime::class ) ? $cpt_value->format( DATE_ATOM ) : $cpt_value; @@ -1203,5 +1213,4 @@ public function backfill( array $args = array(), array $assoc_args = array() ) { ) ); } - } diff --git a/plugins/woocommerce/src/Internal/Admin/Loader.php b/plugins/woocommerce/src/Internal/Admin/Loader.php index f01cd6edafd20..ba29710e9da00 100644 --- a/plugins/woocommerce/src/Internal/Admin/Loader.php +++ b/plugins/woocommerce/src/Internal/Admin/Loader.php @@ -124,12 +124,25 @@ public static function embed_page_header() { $sections = self::get_embed_breadcrumbs(); $sections = is_array( $sections ) ? $sections : array( $sections ); + + $page_title = ''; + $pages_with_tabs = array( 'Settings', 'Reports', 'Status' ); + + if ( + count( $sections ) > 2 && + is_array( $sections[1] ) && + in_array( $sections[1][1], $pages_with_tabs, true ) + ) { + $page_title = $sections[1][1]; + } else { + $page_title = end( $sections ); + } ?>

- +

@@ -293,7 +306,7 @@ public static function add_component_settings( $settings ) { $settings['orderStatuses'] = self::get_order_statuses( wc_get_order_statuses() ); $settings['stockStatuses'] = self::get_order_statuses( wc_get_product_stock_status_options() ); $settings['currency'] = self::get_currency_settings(); - $settings['locale'] = [ + $settings['locale'] = array( 'siteLocale' => isset( $settings['siteLocale'] ) ? $settings['siteLocale'] : get_locale(), @@ -303,7 +316,7 @@ public static function add_component_settings( $settings ) { 'weekdaysShort' => isset( $settings['l10n']['weekdaysShort'] ) ? $settings['l10n']['weekdaysShort'] : array_values( $wp_locale->weekday_abbrev ), - ]; + ); } $preload_data_endpoints = apply_filters( 'woocommerce_component_settings_preload_endpoints', array() ); @@ -327,7 +340,7 @@ public static function add_component_settings( $settings ) { $setting_options = new \WC_REST_Setting_Options_V2_Controller(); foreach ( $preload_settings as $group ) { $group_settings = $setting_options->get_group_settings( $group ); - $preload_settings = []; + $preload_settings = array(); foreach ( $group_settings as $option ) { if ( array_key_exists( 'id', $option ) && array_key_exists( 'value', $option ) ) { $preload_settings[ $option['id'] ] = $option['value']; @@ -374,7 +387,7 @@ public static function add_component_settings( $settings ) { if ( ! empty( $preload_data_endpoints ) ) { $settings['dataEndpoints'] = isset( $settings['dataEndpoints'] ) ? $settings['dataEndpoints'] - : []; + : array(); foreach ( $preload_data_endpoints as $key => $endpoint ) { // Handle error case: rest_do_request() doesn't guarantee success. if ( empty( $preload_data[ $endpoint ] ) ) { diff --git a/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/File.php b/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/File.php index eb0764e2204b4..ff0c04152235e 100644 --- a/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/File.php +++ b/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/File.php @@ -4,7 +4,8 @@ namespace Automattic\WooCommerce\Internal\Admin\Logging\FileV2; use Automattic\Jetpack\Constants; -use WP_Filesystem_Direct; +use Automattic\WooCommerce\Internal\Utilities\FilesystemUtil; +use Exception; /** * File class. @@ -60,14 +61,6 @@ class File { * @param string $path The absolute path of the file. */ public function __construct( $path ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; - - global $wp_filesystem; - - if ( ! $wp_filesystem instanceof WP_Filesystem_Direct ) { - WP_Filesystem(); - } - $this->path = $path; $this->ingest_path(); } @@ -237,27 +230,33 @@ public function has_standard_filename(): bool { /** * Check if the file represented by the class instance is a file and is readable. * - * @global WP_Filesystem_Direct $wp_filesystem - * * @return bool */ public function is_readable(): bool { - global $wp_filesystem; + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + $is_readable = $filesystem->is_file( $this->path ) && $filesystem->is_readable( $this->path ); + } catch ( Exception $exception ) { + return false; + } - return $wp_filesystem->is_file( $this->path ) && $wp_filesystem->is_readable( $this->path ); + return $is_readable; } /** * Check if the file represented by the class instance is a file and is writable. * - * @global WP_Filesystem_Direct $wp_filesystem - * * @return bool */ public function is_writable(): bool { - global $wp_filesystem; + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + $is_writable = $filesystem->is_file( $this->path ) && $filesystem->is_writable( $this->path ); + } catch ( Exception $exception ) { + return false; + } - return $wp_filesystem->is_file( $this->path ) && $wp_filesystem->is_writable( $this->path ); + return $is_writable; } /** @@ -372,31 +371,38 @@ public function get_created_timestamp(): int { /** * Get the time of the last modification of the file, as a Unix timestamp. Or false if the file isn't readable. * - * @global WP_Filesystem_Direct $wp_filesystem - * * @return int|false */ public function get_modified_timestamp() { - global $wp_filesystem; + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + $timestamp = $filesystem->mtime( $this->path ); + } catch ( Exception $exception ) { + return false; + } - return $wp_filesystem->mtime( $this->path ); + return $timestamp; } /** * Get the size of the file in bytes. Or false if the file isn't readable. * - * @global WP_Filesystem_Direct $wp_filesystem - * * @return int|false */ public function get_file_size() { - global $wp_filesystem; + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + + if ( ! $filesystem->is_readable( $this->path ) ) { + return false; + } - if ( ! $wp_filesystem->is_readable( $this->path ) ) { + $size = $filesystem->size( $this->path ); + } catch ( Exception $exception ) { return false; } - return $wp_filesystem->size( $this->path ); + return $size; } /** @@ -405,10 +411,13 @@ public function get_file_size() { * @return bool */ protected function create(): bool { - global $wp_filesystem; - - $created = $wp_filesystem->touch( $this->path ); - $modded = $wp_filesystem->chmod( $this->path ); + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + $created = $filesystem->touch( $this->path ); + $modded = $filesystem->chmod( $this->path ); + } catch ( Exception $exception ) { + return false; + } return $created && $modded; } @@ -421,6 +430,10 @@ protected function create(): bool { * @return bool */ public function write( string $text ): bool { + if ( '' === $text ) { + return false; + } + if ( ! $this->is_writable() ) { $created = $this->create(); @@ -463,8 +476,6 @@ public function rotate(): bool { return false; } - global $wp_filesystem; - $created = 0; if ( $this->has_standard_filename() ) { $created = $this->get_created_timestamp(); @@ -489,7 +500,13 @@ public function rotate(): bool { $new_filename = str_replace( $search, $replace, $old_filename ); $new_path = str_replace( $old_filename, $new_filename, $this->path ); - $moved = $wp_filesystem->move( $this->path, $new_path, true ); + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + $moved = $filesystem->move( $this->path, $new_path, true ); + } catch ( Exception $exception ) { + return false; + } + if ( ! $moved ) { return false; } @@ -503,13 +520,16 @@ public function rotate(): bool { /** * Delete the file from the filesystem. * - * @global WP_Filesystem_Direct $wp_filesystem - * * @return bool True on success, false on failure. */ public function delete(): bool { - global $wp_filesystem; + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + $deleted = $filesystem->delete( $this->path, false, 'f' ); + } catch ( Exception $exception ) { + return false; + } - return $wp_filesystem->delete( $this->path, false, 'f' ); + return $deleted; } } diff --git a/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/FileController.php b/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/FileController.php index 3080e1df41c2e..aec4907eabb11 100644 --- a/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/FileController.php +++ b/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/FileController.php @@ -481,10 +481,7 @@ public function delete_files( array $file_ids ): int { $files = $this->get_files_by_id( $file_ids ); foreach ( $files as $file ) { - $result = false; - if ( $file->is_writable() ) { - $result = $file->delete(); - } + $result = $file->delete(); if ( true === $result ) { $deleted ++; @@ -662,7 +659,7 @@ public function get_log_directory_size(): int { $path = realpath( Settings::get_log_directory() ); if ( wp_is_writable( $path ) ) { - $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $path, \FilesystemIterator::SKIP_DOTS ) ); + $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $path, \FilesystemIterator::SKIP_DOTS ), \RecursiveIteratorIterator::CATCH_GET_CHILD ); foreach ( $iterator as $file ) { $bytes += $file->getSize(); diff --git a/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/FileExporter.php b/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/FileExporter.php index 57a2c816d28bf..92b0045e8709f 100644 --- a/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/FileExporter.php +++ b/plugins/woocommerce/src/Internal/Admin/Logging/FileV2/FileExporter.php @@ -3,8 +3,9 @@ namespace Automattic\WooCommerce\Internal\Admin\Logging\FileV2; +use Automattic\WooCommerce\Internal\Utilities\FilesystemUtil; +use Exception; use WP_Error; -use WP_Filesystem_Direct; /** * FileExport class. @@ -39,11 +40,6 @@ class FileExporter { * part of the path. */ public function __construct( string $path, string $alternate_filename = '' ) { - global $wp_filesystem; - if ( ! $wp_filesystem instanceof WP_Filesystem_Direct ) { - WP_Filesystem(); - } - $this->path = $path; $this->alternate_filename = $alternate_filename; } @@ -54,8 +50,14 @@ public function __construct( string $path, string $alternate_filename = '' ) { * @return WP_Error|void Only returns something if there is an error. */ public function emit_file() { - global $wp_filesystem; - if ( ! $wp_filesystem->is_file( $this->path ) || ! $wp_filesystem->is_readable( $this->path ) ) { + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + $is_readable = ! $filesystem->is_file( $this->path ) || ! $filesystem->is_readable( $this->path ); + } catch ( Exception $exception ) { + $is_readable = false; + } + + if ( ! $is_readable ) { return new WP_Error( 'wc_logs_invalid_file', __( 'Could not access file.', 'woocommerce' ) diff --git a/plugins/woocommerce/src/Internal/Admin/Logging/LogHandlerFileV2.php b/plugins/woocommerce/src/Internal/Admin/Logging/LogHandlerFileV2.php index dd509419cf65f..19c551c672d01 100644 --- a/plugins/woocommerce/src/Internal/Admin/Logging/LogHandlerFileV2.php +++ b/plugins/woocommerce/src/Internal/Admin/Logging/LogHandlerFileV2.php @@ -80,13 +80,15 @@ protected static function format_entry( $timestamp, $level, $message, $context ) $time_string = static::format_time( $timestamp ); $level_string = strtoupper( $level ); - unset( $context['source'] ); - if ( ! empty( $context ) ) { - if ( isset( $context['backtrace'] ) && true === filter_var( $context['backtrace'], FILTER_VALIDATE_BOOLEAN ) ) { - $context['backtrace'] = static::get_backtrace(); - } + if ( isset( $context['backtrace'] ) && true === filter_var( $context['backtrace'], FILTER_VALIDATE_BOOLEAN ) ) { + $context['backtrace'] = static::get_backtrace(); + } + + $context_for_entry = $context; + unset( $context_for_entry['source'] ); - $formatted_context = wp_json_encode( $context ); + if ( ! empty( $context_for_entry ) ) { + $formatted_context = wp_json_encode( $context_for_entry ); $message .= " CONTEXT: $formatted_context"; } diff --git a/plugins/woocommerce/src/Internal/Admin/Logging/Settings.php b/plugins/woocommerce/src/Internal/Admin/Logging/Settings.php index 6995a59d827e2..61adc0087b00e 100644 --- a/plugins/woocommerce/src/Internal/Admin/Logging/Settings.php +++ b/plugins/woocommerce/src/Internal/Admin/Logging/Settings.php @@ -8,10 +8,12 @@ use Automattic\WooCommerce\Internal\Admin\Logging\LogHandlerFileV2; use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\FileController; use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; +use Automattic\WooCommerce\Internal\Utilities\FilesystemUtil; use Automattic\WooCommerce\Proxies\LegacyProxy; +use Exception; use WC_Admin_Settings; use WC_Log_Handler_DB, WC_Log_Handler_File, WC_Log_Levels; -use WP_Filesystem_Base; +use WP_Filesystem_Direct; /** * Settings class. @@ -78,14 +80,13 @@ public static function get_log_directory(): string { if ( true === $result ) { // Create infrastructure to prevent listing contents of the logs directory. - require_once ABSPATH . 'wp-admin/includes/file.php'; - global $wp_filesystem; - if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) { - WP_Filesystem(); + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + $filesystem->put_contents( $dir . '.htaccess', 'deny from all' ); + $filesystem->put_contents( $dir . 'index.html', '' ); + } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Creation failed. } - - $wp_filesystem->put_contents( $dir . '.htaccess', 'deny from all' ); - $wp_filesystem->put_contents( $dir . 'index.html', '' ); } } @@ -292,6 +293,20 @@ private function get_filesystem_settings_definitions(): array { $location_info = array(); $directory = self::get_log_directory(); + $status_info = array(); + try { + $filesystem = FilesystemUtil::get_wp_filesystem(); + if ( $filesystem instanceof WP_Filesystem_Direct ) { + $status_info[] = __( '✅ Ready', 'woocommerce' ); + } else { + $status_info[] = __( '⚠️ The file system is not configured for direct writes. This could cause problems for the logger.', 'woocommerce' ); + $status_info[] = __( 'You may want to switch to the database for log storage.', 'woocommerce' ); + } + } catch ( Exception $exception ) { + $status_info[] = __( '⚠️ The file system connection could not be initialized.', 'woocommerce' ); + $status_info[] = __( 'You may want to switch to the database for log storage.', 'woocommerce' ); + } + $location_info[] = sprintf( // translators: %s is a location in the filesystem. __( 'Log files are stored in this directory: %s', 'woocommerce' ), @@ -317,6 +332,11 @@ private function get_filesystem_settings_definitions(): array { 'id' => self::PREFIX . 'settings', 'type' => 'title', ), + 'file_status' => array( + 'title' => __( 'Status', 'woocommerce' ), + 'type' => 'info', + 'text' => implode( "\n\n", $status_info ), + ), 'log_directory' => array( 'title' => __( 'Location', 'woocommerce' ), 'type' => 'info', @@ -340,7 +360,7 @@ private function get_database_settings_definitions(): array { $table = "{$wpdb->prefix}woocommerce_log"; $location_info = sprintf( - // translators: %s is a location in the filesystem. + // translators: %s is the name of a table in the database. __( 'Log entries are stored in this database table: %s', 'woocommerce' ), "$table" ); diff --git a/plugins/woocommerce/src/Internal/Admin/Notes/WooSubscriptionsNotes.php b/plugins/woocommerce/src/Internal/Admin/Notes/WooSubscriptionsNotes.php index e3e4e4aabb00e..58b389f28c4e0 100644 --- a/plugins/woocommerce/src/Internal/Admin/Notes/WooSubscriptionsNotes.php +++ b/plugins/woocommerce/src/Internal/Admin/Notes/WooSubscriptionsNotes.php @@ -221,6 +221,9 @@ public static function get_note() { * @return int|false */ public function get_product_id_from_subscription_note( &$note ) { + if ( ! is_object( $note ) ) { + return false; + } $content_data = $note->get_content_data(); if ( property_exists( $content_data, 'product_id' ) ) { diff --git a/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/OrderAttribution.php b/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/OrderAttribution.php index ce8b4e2e3564f..83f2cad1a8823 100644 --- a/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/OrderAttribution.php +++ b/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/OrderAttribution.php @@ -66,12 +66,6 @@ public function format_meta_data( array &$meta ) { public function output( WC_Order $order ) { $meta = $this->filter_meta_data( $order->get_meta_data() ); - // If we don't have any meta to show, return. - if ( empty( $meta ) ) { - esc_html_e( 'No order source data available.', 'woocommerce' ); - return; - } - $this->format_meta_data( $meta ); // No more details if there is only the origin value - this is for unknown source types. diff --git a/plugins/woocommerce/src/Internal/BatchProcessing/BatchProcessingController.php b/plugins/woocommerce/src/Internal/BatchProcessing/BatchProcessingController.php index bdb4877ac2f82..210ca8109faee 100644 --- a/plugins/woocommerce/src/Internal/BatchProcessing/BatchProcessingController.php +++ b/plugins/woocommerce/src/Internal/BatchProcessing/BatchProcessingController.php @@ -274,7 +274,9 @@ public function is_scheduled( string $processor_class_name ): bool { * @throws \Exception If it's not possible to get an instance of the class. */ private function get_processor_instance( string $processor_class_name ) : BatchProcessorInterface { - $processor = wc_get_container()->get( $processor_class_name ); + + $container = wc_get_container(); + $processor = $container->has( $processor_class_name ) ? $container->get( $processor_class_name ) : null; /** * Filters the instance of a processor for a given class name. diff --git a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonCacheInvalidator.php b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonCacheInvalidator.php new file mode 100644 index 0000000000000..a2101519adf1d --- /dev/null +++ b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonCacheInvalidator.php @@ -0,0 +1,43 @@ + $cart_page_id, + 'post_status' => 'publish', + ) + ); + } + } +} diff --git a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonHelper.php b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonHelper.php new file mode 100644 index 0000000000000..abd8f4fbdfdb5 --- /dev/null +++ b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonHelper.php @@ -0,0 +1,66 @@ +is_site_live() ) { + return false; + } + + if ( $this->is_site_coming_soon() ) { + return true; + } + + // Check the URL is a store page when in "store coming soon" mode. + if ( $this->is_store_coming_soon() && WCAdminHelper::is_store_page( $url ) ) { + return true; + } + + // Default to false. + return false; + } + + /** + * Builds the relative URL from the WP instance. + * + * @internal + * @link https://wordpress.stackexchange.com/a/274572 + * @param \WP $wp WordPress environment instance. + */ + public function get_url_from_wp( \WP $wp ) { + return home_url( add_query_arg( $wp->query_vars, $wp->request ) ); + } +} diff --git a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonRequestHandler.php b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonRequestHandler.php new file mode 100644 index 0000000000000..cfd4642dcf08f --- /dev/null +++ b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonRequestHandler.php @@ -0,0 +1,142 @@ +coming_soon_helper = $coming_soon_helper; + add_filter( 'template_include', array( $this, 'handle_template_include' ) ); + add_action( 'wp_enqueue_scripts', array( $this, 'deregister_unnecessary_styles' ), 100 ); + } + + /** + * Deregisters unnecessary styles for the coming soon page. + * + * @return void + */ + public function deregister_unnecessary_styles() { + global $wp; + + if ( ! $this->should_show_coming_soon( $wp ) ) { + return; + } + + if ( $this->coming_soon_helper->is_site_coming_soon() ) { + global $wp_styles; + + foreach ( $wp_styles->registered as $handle => $registered_style ) { + // Deregister all styles except for block styles. + if ( + strpos( $handle, 'wp-block' ) !== 0 && + strpos( $handle, 'core-block' ) !== 0 + ) { + wp_deregister_style( $handle ); + } + } + } + } + + /** + * Replaces the page template with a 'coming soon' when the site is in coming soon mode. + * + * @internal + * + * @param string $template The path to the previously determined template. + * @return string|null The path to the 'coming soon' template or null to prevent further template loading in FSE themes. + */ + public function handle_template_include( $template ) { + global $wp; + + if ( ! $this->should_show_coming_soon( $wp ) ) { + return $template; + } + + // A coming soon page needs to be displayed. Don't cache this response. + nocache_headers(); + + add_theme_support( 'block-templates' ); + wp_dequeue_style( 'global-styles' ); + $coming_soon_template = get_query_template( 'coming-soon' ); + + if ( ! wc_current_theme_is_fse_theme() && $this->coming_soon_helper->is_store_coming_soon() ) { + get_header(); + } + + include $coming_soon_template; + + if ( ! wc_current_theme_is_fse_theme() && $this->coming_soon_helper->is_store_coming_soon() ) { + get_footer(); + } + + if ( wc_current_theme_is_fse_theme() ) { + // Since we've already rendered a template, return null to ensure no other template is rendered. + return null; + } else { + // In non-FSE themes, other templates will still be rendered. + // We need to exit to prevent further processing. + exit(); + } + } + + /** + * Determines whether the coming soon screen should be shown. + * + * @param \WP $wp Current WordPress environment instance. + * + * @return bool + */ + private function should_show_coming_soon( \WP &$wp ) { + // Early exit if LYS feature is disabled. + if ( ! Features::is_enabled( 'launch-your-store' ) ) { + return false; + } + + // Early exit if the user is logged in as administrator / shop manager. + if ( current_user_can( 'manage_woocommerce' ) ) { + return false; + } + + // Do not show coming soon on 404 pages when restrict to store pages only. + if ( $this->coming_soon_helper->is_store_coming_soon() && is_404() ) { + return false; + } + + // Early exit if the URL doesn't need a coming soon screen. + $url = $this->coming_soon_helper->get_url_from_wp( $wp ); + + if ( ! $this->coming_soon_helper->is_url_coming_soon( $url ) ) { + return false; + } + + // Exclude users with a private link. + if ( isset( $_GET['woo-share'] ) && get_option( 'woocommerce_share_key' ) === $_GET['woo-share'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended + // Persist the share link with a cookie for 90 days. + setcookie( 'woo-share', sanitize_text_field( wp_unslash( $_GET['woo-share'] ) ), time() + 60 * 60 * 24 * 90, '/' ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended + return false; + } + if ( isset( $_COOKIE['woo-share'] ) && get_option( 'woocommerce_share_key' ) === $_COOKIE['woo-share'] ) { + return false; + } + return true; + } +} diff --git a/plugins/woocommerce/src/Internal/DataStores/CustomMetaDataStore.php b/plugins/woocommerce/src/Internal/DataStores/CustomMetaDataStore.php index 06cca2d7da757..36303ea19845a 100644 --- a/plugins/woocommerce/src/Internal/DataStores/CustomMetaDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/CustomMetaDataStore.php @@ -250,7 +250,9 @@ public function get_meta_keys( $limit = 100, $order = 'ASC', $include_private = $query = "SELECT DISTINCT meta_key FROM {$db_info['table']} "; if ( ! $include_private ) { - $query .= $wpdb->prepare( 'WHERE meta_key NOT LIKE %s ', $wpdb->esc_like( '_' ) . '%' ); + $query .= $wpdb->prepare( "WHERE meta_key !='' AND meta_key NOT BETWEEN '_' AND '_z' AND meta_key NOT LIKE %s ", $wpdb->esc_like( '_' ) . '%' ); + } else { + $query .= "WHERE meta_key != '' "; } $order = in_array( strtoupper( $order ), array( 'ASC', 'DESC' ), true ) ? $order : 'ASC'; diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php index c700e243ac349..feacb0e875e67 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/CustomOrdersTableController.php @@ -10,6 +10,8 @@ use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessingController; use Automattic\WooCommerce\Internal\Features\FeaturesController; use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; +use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil; +use Automattic\WooCommerce\Utilities\OrderUtil; use Automattic\WooCommerce\Utilities\PluginUtil; use WC_Admin_Settings; @@ -46,6 +48,12 @@ class CustomOrdersTableController { public const DEFAULT_DB_TRANSACTIONS_ISOLATION_LEVEL = 'READ UNCOMMITTED'; + public const HPOS_FTS_INDEX_OPTION = 'woocommerce_hpos_fts_index_enabled'; + + public const HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION = 'woocommerce_hpos_address_fts_index_created'; + + public const HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION = 'woocommerce_hpos_order_item_fts_index_created'; + /** * The data store object to use. * @@ -109,6 +117,13 @@ class CustomOrdersTableController { */ private $plugin_util; + /** + * The db util object to use. + * + * @var DatabaseUtil; + */ + private $db_util; + /** * Class constructor. */ @@ -124,6 +139,7 @@ private function init_hooks() { self::add_filter( 'woocommerce_order-refund_data_store', array( $this, 'get_refunds_data_store' ), 999, 1 ); self::add_filter( 'woocommerce_debug_tools', array( $this, 'add_hpos_tools' ), 999 ); self::add_filter( 'updated_option', array( $this, 'process_updated_option' ), 999, 3 ); + self::add_filter( 'updated_option', array( $this, 'process_updated_option_fts_index' ), 999, 3 ); self::add_filter( 'pre_update_option', array( $this, 'process_pre_update_option' ), 999, 3 ); self::add_action( 'woocommerce_after_register_post_type', array( $this, 'register_post_type_for_order_placeholders' ), 10, 0 ); self::add_action( 'woocommerce_sections_advanced', array( $this, 'sync_now' ) ); @@ -144,6 +160,7 @@ private function init_hooks() { * @param OrderCache $order_cache The order cache engine to use. * @param OrderCacheController $order_cache_controller The order cache controller to use. * @param PluginUtil $plugin_util The plugin util to use. + * @param DatabaseUtil $db_util The database util to use. */ final public function init( OrdersTableDataStore $data_store, @@ -154,7 +171,8 @@ final public function init( FeaturesController $features_controller, OrderCache $order_cache, OrderCacheController $order_cache_controller, - PluginUtil $plugin_util + PluginUtil $plugin_util, + DatabaseUtil $db_util ) { $this->data_store = $data_store; $this->data_synchronizer = $data_synchronizer; @@ -165,6 +183,7 @@ final public function init( $this->order_cache = $order_cache; $this->order_cache_controller = $order_cache_controller; $this->plugin_util = $plugin_util; + $this->db_util = $db_util; } /** @@ -291,6 +310,61 @@ private function process_updated_option( $option, $old_value, $value ) { } } + /** + * Process option that enables FTS index on orders table. Tries to create an FTS index when option is enabled. + * + * @param string $option Option name. + * @param string $old_value Old value of the option. + * @param string $value New value of the option. + * + * @return void + */ + private function process_updated_option_fts_index( $option, $old_value, $value ) { + if ( self::HPOS_FTS_INDEX_OPTION !== $option ) { + return; + } + + if ( 'yes' !== $value ) { + return; + } + + if ( ! $this->custom_orders_table_usage_is_enabled() ) { + update_option( self::HPOS_FTS_INDEX_OPTION, 'no', true ); + if ( class_exists( 'WC_Admin_Settings' ) ) { + WC_Admin_Settings::add_error( __( 'Failed to create FTS index on orders table. This feature is only available when High-performance order storage is enabled.', 'woocommerce' ) ); + } + return; + } + + if ( ! $this->db_util->fts_index_on_order_address_table_exists() ) { + $this->db_util->create_fts_index_order_address_table(); + } + + // Check again to see if index was actually created. + if ( $this->db_util->fts_index_on_order_address_table_exists() ) { + update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'yes', true ); + } else { + update_option( self::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION, 'no', true ); + if ( class_exists( 'WC_Admin_Settings ' ) ) { + WC_Admin_Settings::add_error( __( 'Failed to create FTS index on address table', 'woocommerce' ) ); + } + } + + if ( ! $this->db_util->fts_index_on_order_item_table_exists() ) { + $this->db_util->create_fts_index_order_item_table(); + } + + // Check again to see if index was actually created. + if ( $this->db_util->fts_index_on_order_item_table_exists() ) { + update_option( self::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION, 'yes', true ); + } else { + update_option( self::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION, 'no', true ); + if ( class_exists( 'WC_Admin_Settings ' ) ) { + WC_Admin_Settings::add_error( __( 'Failed to create FTS index on order item table', 'woocommerce' ) ); + } + } + } + /** * Handler for the setting pre-update hook. * We use it to verify that authoritative orders table switch doesn't happen while sync is pending. @@ -437,7 +511,7 @@ private function get_hpos_setting_for_feature() { return array(); } - $get_value = function() { + $get_value = function () { return $this->custom_orders_table_usage_is_enabled() ? 'yes' : 'no'; }; @@ -446,18 +520,20 @@ private function get_hpos_setting_for_feature() { * gets called while it's still being instantiated and creates and endless loop. */ - $get_desc = function() { + $get_desc = function () { $plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true ); return $this->plugin_util->generate_incompatible_plugin_feature_warning( 'custom_order_tables', $plugin_compatibility ); }; - $get_disabled = function() { + $get_disabled = function () { $plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true ); $sync_complete = 0 === $this->get_orders_pending_sync_count(); $disabled = array(); // Changing something here? might also want to look at `enable|disable` functions in CLIRunner. - if ( count( array_merge( $plugin_compatibility['uncertain'], $plugin_compatibility['incompatible'] ) ) > 0 ) { + $incompatible_plugins = array_merge( $plugin_compatibility['uncertain'], $plugin_compatibility['incompatible'] ); + $incompatible_plugins = array_diff( $incompatible_plugins, $this->plugin_util->get_plugins_excluded_from_compatibility_ui() ); + if ( count( $incompatible_plugins ) > 0 ) { $disabled = array( 'yes' ); } if ( ! $sync_complete && ! $this->changing_data_source_with_sync_pending_is_allowed() ) { @@ -493,11 +569,11 @@ private function get_hpos_setting_for_sync() { return array(); } - $get_value = function() { + $get_value = function () { return get_option( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION ); }; - $get_sync_message = function() { + $get_sync_message = function () { $orders_pending_sync_count = $this->get_orders_pending_sync_count(); $sync_in_progress = $this->batch_processing_controller->is_enqueued( get_class( $this->data_synchronizer ) ); $sync_enabled = $this->data_synchronizer->data_sync_is_enabled(); @@ -576,7 +652,7 @@ private function get_hpos_setting_for_sync() { return implode( '
', $sync_message ); }; - $get_description_is_error = function() { + $get_description_is_error = function () { $sync_is_pending = $this->get_orders_pending_sync_count() > 0; return $sync_is_pending && $this->changing_data_source_with_sync_pending_is_allowed(); diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index ecae3c7f2979a..6bdb3f3e21a24 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -1631,15 +1631,7 @@ protected function get_order_data_for_ids( $ids ) { ); // phpcs:enable - $meta_data_query = $this->get_order_meta_select_statement(); - $order_data = array(); - $meta_data = $wpdb->get_results( - $wpdb->prepare( - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $meta_data_query and $order_meta_table is autogenerated and should already be prepared. $id_placeholder is already prepared. - "$meta_data_query WHERE $order_meta_table.order_id in ( $id_placeholder )", - $ids - ) - ); + $order_data = array(); foreach ( $table_data as $table_datum ) { $id = $table_datum->{"{$order_table_alias}_id"}; @@ -1663,14 +1655,27 @@ protected function get_order_data_for_ids( $ids ) { $order_data[ $id ]->meta_data = array(); } - foreach ( $meta_data as $meta_datum ) { - // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Not a meta query. - $order_data[ $meta_datum->order_id ]->meta_data[] = (object) array( - 'meta_id' => $meta_datum->id, - 'meta_key' => $meta_datum->meta_key, - 'meta_value' => $meta_datum->meta_value, + if ( count( $order_data ) > 0 ) { + $meta_order_ids = array_keys( $order_data ); + $meta_order_id_placeholder = implode( ', ', array_fill( 0, count( $meta_order_ids ), '%d' ) ); + $meta_data_query = $this->get_order_meta_select_statement(); + $meta_data = $wpdb->get_results( + $wpdb->prepare( + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $meta_data_query and $order_meta_table is autogenerated and should already be prepared. $id_placeholder is already prepared. + "$meta_data_query WHERE $order_meta_table.order_id in ( $meta_order_id_placeholder )", + $ids + ) ); - // phpcs:enable + + foreach ( $meta_data as $meta_datum ) { + // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Not a meta query. + $order_data[ $meta_datum->order_id ]->meta_data[] = (object) array( + 'meta_id' => $meta_datum->id, + 'meta_key' => $meta_datum->meta_key, + 'meta_value' => $meta_datum->meta_value, + ); + // phpcs:enable + } } return $order_data; } diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableSearchQuery.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableSearchQuery.php index ad782ff7d497e..73b6c9e31aa32 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableSearchQuery.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableSearchQuery.php @@ -27,7 +27,7 @@ class OrdersTableSearchQuery { /** * Limits the search to a specific field. * - * @var string + * @var string[] */ private $search_filters; @@ -40,8 +40,8 @@ class OrdersTableSearchQuery { */ public function __construct( OrdersTableQuery $query ) { $this->query = $query; - $this->search_term = urldecode( $query->get( 's' ) ); - $this->search_filters = $this->sanitize_search_filters( urldecode( $query->get( 'search_filter' ) ) ); + $this->search_term = $query->get( 's' ); + $this->search_filters = $this->sanitize_search_filters( $query->get( 'search_filter' ) ?? '' ); } /** @@ -51,7 +51,7 @@ public function __construct( OrdersTableQuery $query ) { * * @return array Array of search filters. */ - private function sanitize_search_filters( string $search_filter ) : array { + private function sanitize_search_filters( string $search_filter ): array { $core_filters = array( 'order_id', 'transaction_id', @@ -112,15 +112,7 @@ private function generate_join(): string { * * @return string JOIN clause. */ - private function generate_join_for_search_filter( $search_filter ) : string { - if ( 'products' === $search_filter ) { - $orders_table = $this->query->get_table_name( 'orders' ); - $items_table = $this->query->get_table_name( 'items' ); - return " - LEFT JOIN $items_table AS search_query_items ON search_query_items.order_id = $orders_table.id - "; - } - + private function generate_join_for_search_filter( $search_filter ): string { /** * Filter to support adding a custom order search filter. * Provide a JOIN clause for a new search filter. This should be used along with `woocommerce_hpos_admin_search_filters` @@ -181,7 +173,7 @@ private function generate_where(): string { * * @return string WHERE clause. */ - private function generate_where_for_search_filter( string $search_filter ) : string { + private function generate_where_for_search_filter( string $search_filter ): string { global $wpdb; $order_table = $this->query->get_table_name( 'orders' ); @@ -208,15 +200,11 @@ private function generate_where_for_search_filter( string $search_filter ) : str } if ( 'products' === $search_filter ) { - return $wpdb->prepare( - 'search_query_items.order_item_name LIKE %s', - '%' . $wpdb->esc_like( $this->search_term ) . '%' - ); + return $this->get_where_for_products(); } if ( 'customers' === $search_filter ) { - $meta_sub_query = $this->generate_where_for_meta_table(); - return "`$order_table`.id IN ( $meta_sub_query ) "; + return $this->get_where_for_customers(); } /** @@ -243,6 +231,74 @@ private function generate_where_for_search_filter( string $search_filter ) : str ); } + /** + * Helper function to generate the WHERE clause for products search. Uses FTS when available. + * + * @return string|null WHERE clause for products search. + */ + private function get_where_for_products() { + global $wpdb; + $items_table = $this->query->get_table_name( 'items' ); + $orders_table = $this->query->get_table_name( 'orders' ); + $fts_enabled = get_option( CustomOrdersTableController::HPOS_FTS_INDEX_OPTION ) === 'yes' && get_option( CustomOrdersTableController::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION ) === 'yes'; + + if ( $fts_enabled ) { + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orders_table and $items_table are hardcoded. + return $wpdb->prepare( + " +$orders_table.id in ( + SELECT order_id FROM $items_table search_query_items WHERE + MATCH ( search_query_items.order_item_name ) AGAINST ( %s IN BOOLEAN MODE ) +) +", + '*' . $wpdb->esc_like( $this->search_term ) . '*' + ); + // phpcs:enable + } + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orders_table and $items_table are hardcoded. + return $wpdb->prepare( + " +$orders_table.id in ( + SELECT order_id FROM $items_table search_query_items WHERE + search_query_items.order_item_name LIKE %s +) +", + '%' . $wpdb->esc_like( $this->search_term ) . '%' + ); + // phpcs:enable + } + + /** + * Helper function to generate the WHERE clause for customers search. Uses FTS when available. + * + * @return string|null WHERE clause for customers search. + */ + private function get_where_for_customers() { + global $wpdb; + $order_table = $this->query->get_table_name( 'orders' ); + $address_table = $this->query->get_table_name( 'addresses' ); + + $fts_enabled = get_option( CustomOrdersTableController::HPOS_FTS_INDEX_OPTION ) === 'yes' && get_option( CustomOrdersTableController::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION ) === 'yes'; + + if ( $fts_enabled ) { + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_table and $address_table are hardcoded. + return $wpdb->prepare( + " +$order_table.id IN ( + SELECT order_id FROM $address_table WHERE + MATCH( $address_table.first_name, $address_table.last_name, $address_table.company, $address_table.address_1, $address_table.address_2, $address_table.city, $address_table.state, $address_table.postcode, $address_table.country, $address_table.email ) AGAINST ( %s IN BOOLEAN MODE ) +) +", + '*' . $wpdb->esc_like( $this->search_term ) . '*' + ); + // phpcs:enable + } + + $meta_sub_query = $this->generate_where_for_meta_table(); + return "`$order_table`.id IN ( $meta_sub_query ) "; + } + /** * Generates where clause for meta table. * diff --git a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/ComingSoonServiceProvider.php b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/ComingSoonServiceProvider.php new file mode 100644 index 0000000000000..ba544bfaba0c8 --- /dev/null +++ b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/ComingSoonServiceProvider.php @@ -0,0 +1,34 @@ +add( ComingSoonCacheInvalidator::class ); + $this->add( ComingSoonHelper::class ); + $this->add( ComingSoonRequestHandler::class )->addArgument( ComingSoonHelper::class ); + } +} diff --git a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/OrdersDataStoreServiceProvider.php b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/OrdersDataStoreServiceProvider.php index e8bd45aebc1b6..6b37c82918b95 100644 --- a/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/OrdersDataStoreServiceProvider.php +++ b/plugins/woocommerce/src/Internal/DependencyManagement/ServiceProviders/OrdersDataStoreServiceProvider.php @@ -76,6 +76,7 @@ public function register() { OrderCache::class, OrderCacheController::class, PluginUtil::class, + DatabaseUtil::class, ) ); $this->share( OrderCache::class ); diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index fe0a9a518a1c7..be36ab7008802 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -7,6 +7,7 @@ use Automattic\WooCommerce\Internal\Admin\Analytics; use Automattic\WooCommerce\Admin\Features\Navigation\Init; +use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; use Automattic\WooCommerce\Proxies\LegacyProxy; use Automattic\WooCommerce\Utilities\ArrayUtil; @@ -232,6 +233,17 @@ private function get_feature_definitions() { 'is_legacy' => true, 'is_experimental' => false, ), + 'hpos_fts_indexes' => array( + 'name' => __( 'HPOS Full text search indexes', 'woocommerce' ), + 'description' => __( + 'Create and use full text search indexes for orders. This feature only works with high-performance order storage.', + 'woocommerce' + ), + 'is_experimental' => true, + 'enabled_by_default' => false, + 'is_legacy' => true, + 'option_key' => CustomOrdersTableController::HPOS_FTS_INDEX_OPTION, + ), ); foreach ( $legacy_features as $slug => $definition ) { @@ -392,7 +404,7 @@ public function declare_compatibility( string $feature_id, string $plugin_name, ArrayUtil::ensure_key_is_array( $this->compatibility_info_by_plugin[ $plugin_name ], $opposite_key ); if ( in_array( $feature_id, $this->compatibility_info_by_plugin[ $plugin_name ][ $opposite_key ], true ) ) { - throw new \Exception( "Plugin $plugin_name is trying to declare itself as $key with the '$feature_id' feature, but it already declared itself as $opposite_key" ); + throw new \Exception( esc_html( "Plugin $plugin_name is trying to declare itself as $key with the '$feature_id' feature, but it already declared itself as $opposite_key" ) ); } if ( ! in_array( $feature_id, $this->compatibility_info_by_plugin[ $plugin_name ][ $key ], true ) ) { @@ -487,14 +499,15 @@ public function get_compatible_plugins_for_feature( string $feature_id, bool $ac /** * Check if the 'woocommerce_init' has run or is running, do a 'wc_doing_it_wrong' if not. * - * @param string|null $function Name of the invoking method, if not null, 'wc_doing_it_wrong' will be invoked if 'woocommerce_init' has not run and is not running. + * @param string|null $function_name Name of the invoking method, if not null, 'wc_doing_it_wrong' will be invoked if 'woocommerce_init' has not run and is not running. + * * @return bool True if 'woocommerce_init' has run or is running, false otherwise. */ - private function verify_did_woocommerce_init( string $function = null ): bool { + private function verify_did_woocommerce_init( string $function_name = null ): bool { if ( ! $this->proxy->call_function( 'did_action', 'woocommerce_init' ) && ! $this->proxy->call_function( 'doing_action', 'woocommerce_init' ) ) { - if ( ! is_null( $function ) ) { - $class_and_method = ( new \ReflectionClass( $this ) )->getShortName() . '::' . $function; + if ( ! is_null( $function_name ) ) { + $class_and_method = ( new \ReflectionClass( $this ) )->getShortName() . '::' . $function_name; /* translators: 1: class::method 2: plugins_loaded */ $this->proxy->call_function( 'wc_doing_it_wrong', $class_and_method, sprintf( __( '%1$s should not be called before the %2$s action.', 'woocommerce' ), $class_and_method, 'woocommerce_init' ), '7.0' ); } @@ -869,41 +882,41 @@ private function handle_plugin_deactivation( $plugin_name ): void { * if we are in the plugins page and the query string of the current request * looks like '?plugin_status=incompatible_with_feature&feature_id='. * - * @param array $list The original list of plugins. + * @param array $plugin_list The original list of plugins. */ - private function filter_plugins_list( $list ): array { + private function filter_plugins_list( $plugin_list ): array { if ( ! $this->verify_did_woocommerce_init() ) { - return $list; + return $plugin_list; } // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput if ( ! function_exists( 'get_current_screen' ) || get_current_screen() && 'plugins' !== get_current_screen()->id || 'incompatible_with_feature' !== ArrayUtil::get_value_or_default( $_GET, 'plugin_status' ) ) { - return $list; + return $plugin_list; } $feature_id = $_GET['feature_id'] ?? 'all'; if ( 'all' !== $feature_id && ! $this->feature_exists( $feature_id ) ) { - return $list; + return $plugin_list; } - return $this->get_incompatible_plugins( $feature_id, $list ); + return $this->get_incompatible_plugins( $feature_id, $plugin_list ); } /** * Returns the list of plugins incompatible with a given feature. * * @param string $feature_id ID of the feature. Can also be `all` to denote all features. - * @param array $list List of plugins to filter. + * @param array $plugin_list List of plugins to filter. * * @return array List of plugins incompatible with the given feature. */ - private function get_incompatible_plugins( $feature_id, $list ) { + private function get_incompatible_plugins( $feature_id, $plugin_list ) { $incompatibles = array(); - $list = array_diff_key( $list, array_flip( $this->plugins_excluded_from_compatibility_ui ) ); + $plugin_list = array_diff_key( $plugin_list, array_flip( $this->plugins_excluded_from_compatibility_ui ) ); // phpcs:enable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput - foreach ( array_keys( $list ) as $plugin_name ) { + foreach ( array_keys( $plugin_list ) as $plugin_name ) { if ( ! $this->plugin_util->is_woocommerce_aware_plugin( $plugin_name ) || ! $this->proxy->call_function( 'is_plugin_active', $plugin_name ) ) { continue; } @@ -921,7 +934,7 @@ function ( $feature_id ) { } } - return array_intersect_key( $list, array_flip( $incompatibles ) ); + return array_intersect_key( $plugin_list, array_flip( $incompatibles ) ); } /** diff --git a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/DownloadableProductTrait.php b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/DownloadableProductTrait.php index c884016dab395..63a27717cc1e0 100644 --- a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/DownloadableProductTrait.php +++ b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/DownloadableProductTrait.php @@ -38,11 +38,14 @@ private function add_downloadable_product_blocks( $parent_block ) { $product_downloads_section_group->add_block( array( 'id' => 'product-downloadable', - 'blockName' => 'woocommerce/product-checkbox-field', + 'blockName' => 'woocommerce/product-toggle-field', 'order' => 10, 'attributes' => array( - 'property' => 'downloadable', - 'label' => __( 'Include downloads', 'woocommerce' ), + 'property' => 'downloadable', + 'label' => __( 'Include downloads', 'woocommerce' ), + 'checkedHelp' => __( 'Add any files you\'d like to make available for the customer to download after purchasing, such as instructions or warranty info.', 'woocommerce' ), + 'uncheckedHelp' => __( 'Add any files you\'d like to make available for the customer to download after purchasing, such as instructions or warranty info.', 'woocommerce' ), + ), ) ); diff --git a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php index 89a2e125f0831..5ee8346bc7df0 100644 --- a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php +++ b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php @@ -64,7 +64,7 @@ public function get_description(): string { /** * Adds the group blocks to the template. */ - private function add_group_blocks() { + protected function add_group_blocks() { $this->add_group( array( 'id' => $this::GROUP_IDS['GENERAL'], @@ -106,7 +106,7 @@ private function add_group_blocks() { /** * Adds the general group blocks to the template. */ - private function add_general_group_blocks() { + protected function add_general_group_blocks() { $general_group = $this->get_group_by_id( $this::GROUP_IDS['GENERAL'] ); $general_group->add_block( array( @@ -193,7 +193,7 @@ private function add_general_group_blocks() { /** * Adds the pricing group blocks to the template. */ - private function add_pricing_group_blocks() { + protected function add_pricing_group_blocks() { $is_calc_taxes_enabled = wc_tax_enabled(); $pricing_group = $this->get_group_by_id( $this::GROUP_IDS['PRICING'] ); @@ -315,7 +315,7 @@ private function add_pricing_group_blocks() { /** * Adds the inventory group blocks to the template. */ - private function add_inventory_group_blocks() { + protected function add_inventory_group_blocks() { $inventory_group = $this->get_group_by_id( $this::GROUP_IDS['INVENTORY'] ); $inventory_group->add_block( array( @@ -425,7 +425,7 @@ private function add_inventory_group_blocks() { /** * Adds the shipping group blocks to the template. */ - private function add_shipping_group_blocks() { + protected function add_shipping_group_blocks() { $shipping_group = $this->get_group_by_id( $this::GROUP_IDS['SHIPPING'] ); $shipping_group->add_block( array( @@ -471,7 +471,7 @@ private function add_shipping_group_blocks() { 'title' => __( 'Fees & dimensions', 'woocommerce' ), 'description' => sprintf( /* translators: %1$s: How to get started? link opening tag. %2$s: How to get started? link closing tag.*/ - __( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s.', 'woocommerce' ), + __( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s', 'woocommerce' ), '', '' ), diff --git a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php index 6d237a2fe5468..fd7297ad69c1e 100644 --- a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php +++ b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php @@ -439,6 +439,7 @@ private function add_organization_group_blocks() { 'createTitle' => __( 'Create new category', 'woocommerce' ), 'dialogNameHelpText' => __( 'Shown to customers on the product page.', 'woocommerce' ), 'parentTaxonomyText' => __( 'Parent category', 'woocommerce' ), + 'placeholder' => __( 'Search or create categories…', 'woocommerce' ), ), ) ); @@ -539,8 +540,8 @@ private function add_organization_group_blocks() { 'title' => __( 'Custom fields', 'woocommerce' ), 'description' => sprintf( /* translators: %1$s: Custom fields guide link opening tag. %2$s: Custom fields guide link closing tag. */ - __( 'Custom fields can be used in a variety of ways, such as sharing more detailed product information, showing more input fields, or internal inventory organization. %1$sRead more about custom fields%2$s', 'woocommerce' ), - '', + __( 'Custom fields can be used in a variety of ways, such as sharing more detailed product information, showing more input fields, or for internal inventory organization. %1$sRead more about custom fields%2$s', 'woocommerce' ), + '', '' ), ), @@ -1060,7 +1061,7 @@ private function add_shipping_group_blocks() { 'title' => __( 'Fees & dimensions', 'woocommerce' ), 'description' => sprintf( /* translators: %1$s: How to get started? link opening tag. %2$s: How to get started? link closing tag.*/ - __( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s.', 'woocommerce' ), + __( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s', 'woocommerce' ), '', '' ), @@ -1153,8 +1154,8 @@ private function add_linked_products_group_blocks() { 'attributes' => array( 'title' => __( 'Upsells', 'woocommerce' ), 'description' => sprintf( - /* translators: %1$s: Learn more about linked products. %2$s: Learn more about linked products.*/ - __( 'Upsells are typically products that are extra profitable or better quality or more expensive. Experiment with combinations to boost sales. %1$sLearn more about linked products.%2$s', 'woocommerce' ), + /* translators: %1$s: "Learn more about linked products" link opening tag. %2$s: "Learn more about linked products" link closing tag. */ + __( 'Upsells are typically products that are extra profitable or better quality or more expensive. Experiment with combinations to boost sales. %1$sLearn more about linked products%2$s', 'woocommerce' ), '
', '' ), @@ -1186,8 +1187,8 @@ private function add_linked_products_group_blocks() { 'attributes' => array( 'title' => __( 'Cross-sells', 'woocommerce' ), 'description' => sprintf( - /* translators: %1$s: Learn more about linked products. %2$s: Learn more about linked products.*/ - __( 'By suggesting complementary products in the cart using cross-sells, you can significantly increase the average order value. %1$sLearn more about linked products.%2$s', 'woocommerce' ), + /* translators: %1$s: "Learn more about linked products" link opening tag. %2$s: "Learn more about linked products" link closing tag. */ + __( 'By suggesting complementary products in the cart using cross-sells, you can significantly increase the average order value. %1$sLearn more about linked products%2$s', 'woocommerce' ), '
', '' ), diff --git a/plugins/woocommerce/src/Internal/Orders/OrderAttributionController.php b/plugins/woocommerce/src/Internal/Orders/OrderAttributionController.php index b92b4dbb7b001..c359696fe1f3c 100644 --- a/plugins/woocommerce/src/Internal/Orders/OrderAttributionController.php +++ b/plugins/woocommerce/src/Internal/Orders/OrderAttributionController.php @@ -335,9 +335,6 @@ private function output_origin_column( WC_Order $order ) { $source_type = $order->get_meta( $this->get_meta_prefixed_field_name( 'source_type' ) ); $source = $order->get_meta( $this->get_meta_prefixed_field_name( 'utm_source' ) ); $origin = $this->get_origin_label( $source_type, $source ); - if ( empty( $origin ) ) { - $origin = __( 'Unknown', 'woocommerce' ); - } echo esc_html( $origin ); } diff --git a/plugins/woocommerce/src/Internal/Utilities/COTMigrationUtil.php b/plugins/woocommerce/src/Internal/Utilities/COTMigrationUtil.php index eb12309b83b2c..01a78d99ae118 100644 --- a/plugins/woocommerce/src/Internal/Utilities/COTMigrationUtil.php +++ b/plugins/woocommerce/src/Internal/Utilities/COTMigrationUtil.php @@ -74,8 +74,12 @@ private function custom_orders_table_usage_is_enabled() : bool { * @return bool */ public function is_custom_order_tables_in_sync() : bool { + if ( ! $this->data_synchronizer->data_sync_is_enabled() ) { + return false; + } + $sync_status = $this->data_synchronizer->get_sync_status(); - return 0 === $sync_status['current_pending_count'] && $this->data_synchronizer->data_sync_is_enabled(); + return 0 === $sync_status['current_pending_count']; } /** diff --git a/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php b/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php index b989c2d78f317..3360db18b0497 100644 --- a/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php +++ b/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php @@ -170,7 +170,7 @@ public function format_object_value_for_db( $value, string $type ) { $value = $value ? ( new DateTime( "@{$value}" ) )->format( 'Y-m-d H:i:s' ) : null; break; default: - throw new \Exception( 'Invalid type received: ' . $type ); + throw new \Exception( esc_html( 'Invalid type received: ' . $type ) ); } return $value; @@ -194,7 +194,7 @@ public function get_wpdb_format_for_type( string $type ) { ); if ( ! isset( $wpdb_placeholder_for_type[ $type ] ) ) { - throw new \Exception( 'Invalid column type: ' . $type ); + throw new \Exception( esc_html( 'Invalid column type: ' . $type ) ); } return $wpdb_placeholder_for_type[ $type ]; @@ -231,7 +231,7 @@ public function generate_on_duplicate_statement_clause( array $columns ): string * * @return int Returns the value of DB's ON DUPLICATE KEY UPDATE clause. */ - public function insert_on_duplicate_key_update( $table_name, $data, $format ) : int { + public function insert_on_duplicate_key_update( $table_name, $data, $format ): int { global $wpdb; if ( empty( $data ) ) { return 0; @@ -249,7 +249,7 @@ public function insert_on_duplicate_key_update( $table_name, $data, $format ) : $values[] = $value; $value_format[] = $format[ $index ]; } - $index++; + ++$index; } $column_clause = '`' . implode( '`, `', $columns ) . '`'; $value_format_clause = implode( ', ', $value_format ); @@ -273,7 +273,7 @@ public function insert_on_duplicate_key_update( $table_name, $data, $format ) : * * @return int Max index length. */ - public function get_max_index_length() : int { + public function get_max_index_length(): int { /** * Filters the maximum index length in the database. * @@ -291,4 +291,52 @@ public function get_max_index_length() : int { // Index length cannot be more than 768, which is 3078 bytes in utf8mb4 and max allowed by InnoDB engine. return min( absint( $max_index_length ), 767 ); } + + /** + * Create a fulltext index on order address table. + * + * @return void + */ + public function create_fts_index_order_address_table(): void { + global $wpdb; + $address_table = $wpdb->prefix . 'wc_order_addresses'; + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $address_table is hardcoded. + $wpdb->query( "CREATE FULLTEXT INDEX order_addresses_fts ON $address_table (first_name, last_name, company, address_1, address_2, city, state, postcode, country, email)" ); + } + + /** + * Check if fulltext index with key `order_addresses_fts` on order address table exists. + * + * @return bool + */ + public function fts_index_on_order_address_table_exists(): bool { + global $wpdb; + $address_table = $wpdb->prefix . 'wc_order_addresses'; + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $address_table is hardcoded. + return ! empty( $wpdb->get_results( "SHOW INDEX FROM $address_table WHERE Key_name = 'order_addresses_fts'" ) ); + } + + /** + * Create a fulltext index on order item table. + * + * @return void + */ + public function create_fts_index_order_item_table(): void { + global $wpdb; + $order_item_table = $wpdb->prefix . 'woocommerce_order_items'; + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_item_table is hardcoded. + $wpdb->query( "CREATE FULLTEXT INDEX order_item_fts ON $order_item_table (order_item_name)" ); + } + + /** + * Check if fulltext index with key `order_item_fts` on order item table exists. + * + * @return bool + */ + public function fts_index_on_order_item_table_exists(): bool { + global $wpdb; + $order_item_table = $wpdb->prefix . 'woocommerce_order_items'; + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_item_table is hardcoded. + return ! empty( $wpdb->get_results( "SHOW INDEX FROM $order_item_table WHERE Key_name = 'order_item_fts'" ) ); + } } diff --git a/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php b/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php new file mode 100644 index 0000000000000..4bac73265b39b --- /dev/null +++ b/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php @@ -0,0 +1,64 @@ + \WP_REST_Server::CREATABLE, - 'callback' => [ $this, 'get_response' ], - 'permission_callback' => [ Middleware::class, 'is_authorized' ], - 'args' => [ - 'business_description' => [ + 'callback' => array( $this, 'get_response' ), + 'permission_callback' => array( Middleware::class, 'is_authorized' ), + 'args' => array( + 'business_description' => array( 'description' => __( 'The business description for a given store.', 'woocommerce' ), 'type' => 'string', - ], - ], - ], - 'schema' => [ $this->schema, 'get_public_item_schema' ], - 'allow_batch' => [ 'v1' => true ], - ]; + ), + ), + ), + 'schema' => array( $this->schema, 'get_public_item_schema' ), + 'allow_batch' => array( 'v1' => true ), + ); } /** @@ -100,8 +100,8 @@ protected function get_route_post_response( \WP_REST_Request $request ) { ); } - $store_title = get_option( 'blogname' ); - $previous_ai_generated_title = get_option( 'ai_generated_site_title' ); + $store_title = html_entity_decode( get_option( 'blogname' ) ); + $previous_ai_generated_title = html_entity_decode( get_option( 'ai_generated_site_title' ) ); if ( self::DEFAULT_TITLE === $store_title || ( ! empty( $store_title ) && $previous_ai_generated_title !== $store_title ) ) { return rest_ensure_response( array( 'ai_content_generated' => false ) ); diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/CartUpdateCustomer.php b/plugins/woocommerce/src/StoreApi/Routes/V1/CartUpdateCustomer.php index ae56f62ac45fb..ed3f6a66e8e5d 100644 --- a/plugins/woocommerce/src/StoreApi/Routes/V1/CartUpdateCustomer.php +++ b/plugins/woocommerce/src/StoreApi/Routes/V1/CartUpdateCustomer.php @@ -199,10 +199,10 @@ protected function get_route_post_response( \WP_REST_Request $request ) { // We save them one by one, and we add the group prefix. foreach ( $additional_shipping_values as $key => $value ) { - $this->additional_fields_controller->persist_field_for_customer( "/shipping/{$key}", $value, $customer ); + $this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'shipping' ); } foreach ( $additional_billing_values as $key => $value ) { - $this->additional_fields_controller->persist_field_for_customer( "/billing/{$key}", $value, $customer ); + $this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'billing' ); } wc_do_deprecated_action( @@ -244,20 +244,7 @@ protected function get_customer_billing_address( \WC_Customer $customer ) { $billing_country = $customer->get_billing_country(); $billing_state = $customer->get_billing_state(); - $additional_fields = $this->additional_fields_controller->get_all_fields_from_customer( $customer ); - - $additional_fields = array_reduce( - array_keys( $additional_fields ), - function( $carry, $key ) use ( $additional_fields ) { - if ( 0 === strpos( $key, '/billing/' ) ) { - $value = $additional_fields[ $key ]; - $key = str_replace( '/billing/', '', $key ); - $carry[ $key ] = $value; - } - return $carry; - }, - array() - ); + $additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $customer, 'billing' ); /** * There's a bug in WooCommerce core in which not having a state ("") would result in us validating against the store's state. @@ -293,20 +280,8 @@ function( $carry, $key ) use ( $additional_fields ) { * @return array */ protected function get_customer_shipping_address( \WC_Customer $customer ) { - $additional_fields = $this->additional_fields_controller->get_all_fields_from_customer( $customer ); - - $additional_fields = array_reduce( - array_keys( $additional_fields ), - function( $carry, $key ) use ( $additional_fields ) { - if ( 0 === strpos( $key, '/shipping/' ) ) { - $value = $additional_fields[ $key ]; - $key = str_replace( '/shipping/', '', $key ); - $carry[ $key ] = $value; - } - return $carry; - }, - array() - ); + $additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $customer, 'shipping' ); + return array_merge( [ 'first_name' => $customer->get_shipping_first_name(), diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php index e639967390338..38705c34886ab 100644 --- a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php +++ b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php @@ -428,7 +428,7 @@ private function update_customer_from_request( \WP_REST_Request $request ) { if ( is_callable( [ $customer, $callback ] ) ) { $customer->$callback( $value ); } elseif ( $this->additional_fields_controller->is_field( $key ) ) { - $this->additional_fields_controller->persist_field_for_customer( "/billing/$key", $value, $customer ); + $this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'billing' ); } } @@ -440,7 +440,7 @@ private function update_customer_from_request( \WP_REST_Request $request ) { if ( is_callable( [ $customer, $callback ] ) ) { $customer->$callback( $value ); } elseif ( $this->additional_fields_controller->is_field( $key ) ) { - $this->additional_fields_controller->persist_field_for_customer( "/shipping/$key", $value, $customer ); + $this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer, 'shipping' ); } } diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/BillingAddressSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/BillingAddressSchema.php index cf7cc6d80615c..793153d436ceb 100644 --- a/plugins/woocommerce/src/StoreApi/Schemas/V1/BillingAddressSchema.php +++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/BillingAddressSchema.php @@ -101,27 +101,9 @@ public function get_item_response( $address ) { $billing_state = ''; } - if ( $address instanceof \WC_Order ) { - // get additional fields from order. - $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_order( $address ); - } elseif ( $address instanceof \WC_Customer ) { - // get additional fields from customer. - $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_customer( $address ); - } + $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_object( $address, 'billing' ); - $additional_address_fields = array_reduce( - array_keys( $additional_address_fields ), - function( $carry, $key ) use ( $additional_address_fields ) { - if ( 0 === strpos( $key, '/billing/' ) ) { - $value = $additional_address_fields[ $key ]; - $key = str_replace( '/billing/', '', $key ); - $carry[ $key ] = $value; - } - return $carry; - }, - [] - ); - $address_object = \array_merge( + $address_object = \array_merge( [ 'first_name' => $address->get_billing_first_name(), 'last_name' => $address->get_billing_last_name(), diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/CheckoutSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/CheckoutSchema.php index d175f05d985ec..d61c84d5af5df 100644 --- a/plugins/woocommerce/src/StoreApi/Schemas/V1/CheckoutSchema.php +++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/CheckoutSchema.php @@ -261,20 +261,26 @@ function ( $key, $value ) { * @return array */ protected function get_additional_fields_response( \WC_Order $order ) { - $fields = wp_parse_args( - $this->additional_fields_controller->get_all_fields_from_order( $order ), - $this->additional_fields_controller->get_all_fields_from_customer( wc()->customer ) + $fields = wp_parse_args( + $this->additional_fields_controller->get_all_fields_from_object( $order, 'other' ), + $this->additional_fields_controller->get_all_fields_from_object( wc()->customer, 'other' ) ); - $response = []; + $additional_field_schema = $this->get_additional_fields_schema(); foreach ( $fields as $key => $value ) { - if ( 0 === strpos( $key, '/billing/' ) || 0 === strpos( $key, '/shipping/' ) ) { + if ( ! isset( $additional_field_schema[ $key ] ) ) { + unset( $fields[ $key ] ); continue; } - $response[ $key ] = $value; + // This makes sure we're casting checkboxes from "1" and "0" to boolean. In the frontend, "0" is treated as truthy. + if ( isset( $additional_field_schema[ $key ]['type'] ) && 'boolean' === $additional_field_schema[ $key ]['type'] ) { + $fields[ $key ] = (bool) $value; + } else { + $fields[ $key ] = $this->prepare_html_response( $value ); + } } - return $response; + return $fields; } /** @@ -285,7 +291,7 @@ protected function get_additional_fields_response( \WC_Order $order ) { protected function get_additional_fields_schema() { return $this->generate_additional_fields_schema( $this->additional_fields_controller->get_fields_for_location( 'contact' ), - $this->additional_fields_controller->get_fields_for_location( 'additional' ) + $this->additional_fields_controller->get_fields_for_location( 'order' ) ); } @@ -414,11 +420,11 @@ public function validate_additional_fields( $fields, $request ) { } // Validate groups of properties per registered location. - $locations = array( 'contact', 'additional' ); + $locations = array( 'contact', 'order' ); foreach ( $locations as $location ) { $location_fields = $this->additional_fields_controller->filter_fields_for_location( $fields, $location ); - $result = $this->additional_fields_controller->validate_fields_for_location( $location_fields, $location ); + $result = $this->additional_fields_controller->validate_fields_for_location( $location_fields, $location, 'other' ); if ( is_wp_error( $result ) && $result->has_errors() ) { $errors->merge_from( $result ); diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/ShippingAddressSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/ShippingAddressSchema.php index 0877e301c06df..26c0da47f3649 100644 --- a/plugins/woocommerce/src/StoreApi/Schemas/V1/ShippingAddressSchema.php +++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/ShippingAddressSchema.php @@ -42,27 +42,9 @@ public function get_item_response( $address ) { $shipping_state = ''; } - if ( $address instanceof \WC_Order ) { - // get additional fields from order. - $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_order( $address ); - } elseif ( $address instanceof \WC_Customer ) { - // get additional fields from customer. - $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_customer( $address ); - } + $additional_address_fields = $this->additional_fields_controller->get_all_fields_from_object( $address, 'shipping' ); - $additional_address_fields = array_reduce( - array_keys( $additional_address_fields ), - function( $carry, $key ) use ( $additional_address_fields ) { - if ( 0 === strpos( $key, '/shipping/' ) ) { - $value = $additional_address_fields[ $key ]; - $key = str_replace( '/shipping/', '', $key ); - $carry[ $key ] = $value; - } - return $carry; - }, - [] - ); - $address_object = array_merge( + $address_object = array_merge( [ 'first_name' => $address->get_shipping_first_name(), 'last_name' => $address->get_shipping_last_name(), diff --git a/plugins/woocommerce/src/StoreApi/Utilities/ArrayUtils.php b/plugins/woocommerce/src/StoreApi/Utilities/ArrayUtils.php index 7624cea340046..bea5b8a6aa2a1 100644 --- a/plugins/woocommerce/src/StoreApi/Utilities/ArrayUtils.php +++ b/plugins/woocommerce/src/StoreApi/Utilities/ArrayUtils.php @@ -16,7 +16,7 @@ class ArrayUtils { public static function natural_language_join( $array, $enclose_items_with_quotes = false ) { if ( true === $enclose_items_with_quotes ) { $array = array_map( - function( $item ) { + function ( $item ) { return '"' . $item . '"'; }, $array @@ -33,4 +33,21 @@ function( $item ) { } return $last; } + + /** + * Check if a string contains any of the items in an array. + * + * @param string $needle The string to check. + * @param array $haystack The array of items to check for. + * + * @return bool true if the string contains any of the items in the array, false otherwise. + */ + public static function string_contains_array( $needle, $haystack ) { + foreach ( $haystack as $item ) { + if ( false !== strpos( $needle, $item ) ) { + return true; + } + } + return false; + } } diff --git a/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php b/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php index d811207d54bf6..85726849add82 100644 --- a/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php +++ b/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php @@ -194,17 +194,16 @@ private function persist_additional_fields_for_order( \WP_REST_Request $request $request_fields = $request['additional_fields'] ?? []; foreach ( $request_fields as $key => $value ) { try { - $this->additional_fields_controller->validate_field_for_location( $key, $value, 'additional' ); + $this->additional_fields_controller->validate_field_for_location( $key, $value, 'order' ); } catch ( \Exception $e ) { $errors[] = $e->getMessage(); continue; } - $this->additional_fields_controller->persist_field_for_order( $key, $value, $this->order, false ); + $this->additional_fields_controller->persist_field_for_order( $key, $value, $this->order, 'other', false ); } if ( $errors->has_errors() ) { throw new RouteException( 'woocommerce_rest_checkout_invalid_additional_fields', $errors->get_error_messages(), 400 ); } - } } diff --git a/plugins/woocommerce/src/StoreApi/Utilities/LocalPickupUtils.php b/plugins/woocommerce/src/StoreApi/Utilities/LocalPickupUtils.php index 3f3e894e2377e..4aef995960668 100644 --- a/plugins/woocommerce/src/StoreApi/Utilities/LocalPickupUtils.php +++ b/plugins/woocommerce/src/StoreApi/Utilities/LocalPickupUtils.php @@ -9,24 +9,31 @@ class LocalPickupUtils { /** * Gets the local pickup location settings. + * + * @param string $context The context for the settings. Defaults to 'view'. */ - public static function get_local_pickup_settings() { + public static function get_local_pickup_settings( $context = 'view' ) { $pickup_location_settings = get_option( 'woocommerce_pickup_location_settings', [ 'enabled' => 'no', - 'title' => __( 'Local Pickup', 'woocommerce' ), + 'title' => __( 'Pickup', 'woocommerce' ), ] ); if ( empty( $pickup_location_settings['title'] ) ) { - $pickup_location_settings['title'] = __( 'Local Pickup', 'woocommerce' ); + $pickup_location_settings['title'] = __( 'Pickup', 'woocommerce' ); } if ( empty( $pickup_location_settings['enabled'] ) ) { $pickup_location_settings['enabled'] = 'no'; } + // Return settings as is if we're editing them. + if ( 'edit' === $context ) { + return $pickup_location_settings; + } + // All consumers of this turn it into a bool eventually. Doing it here removes the need for that. $pickup_location_settings['enabled'] = wc_string_to_bool( $pickup_location_settings['enabled'] ); $pickup_location_settings['title'] = wc_clean( $pickup_location_settings['title'] ); diff --git a/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php b/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php index b8c0e87b62114..a5f84b8bf5f05 100644 --- a/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php +++ b/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php @@ -1,7 +1,7 @@ customer ) ) { @@ -148,14 +148,11 @@ public function sync_customer_data_with_order( \WC_Order $order ) { 'shipping_phone' => $order->get_shipping_phone(), ) ); - $order_fields = $this->additional_fields_controller->get_all_fields_from_order( $order ); - $customer_fields = $this->additional_fields_controller->filter_fields_for_customer( $order_fields ); - foreach ( $customer_fields as $key => $value ) { - $this->additional_fields_controller->persist_field_for_customer( $key, $value, $customer ); - } + $this->additional_fields_controller->sync_customer_additional_fields_with_order( $order, $customer ); + $customer->save(); - }; + } } /** @@ -202,7 +199,7 @@ protected function validate_coupons( \WC_Order $order ) { try { array_walk( $validators, - function( $validator, $index, $params ) { + function ( $validator, $index, $params ) { call_user_func_array( array( $this, $validator ), $params ); }, array( $coupon, $order ) @@ -384,20 +381,15 @@ protected function validate_address_fields( \WC_Order $order, $address_type, \WP $address = $order->get_address( $address_type ); $current_locale = isset( $all_locales[ $address['country'] ] ) ? $all_locales[ $address['country'] ] : array(); - $additional_fields = $this->additional_fields_controller->get_all_fields_from_order( $order ); + $additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $order, $address_type ); - foreach ( $additional_fields as $field_id => $field_value ) { - $prefix = '/' . $address_type . '/'; - if ( strpos( $field_id, $prefix ) === 0 ) { - $address[ str_replace( $prefix, '', $field_id ) ] = $field_value; - } - } + $address = array_merge( $address, $additional_fields ); $fields = $this->additional_fields_controller->get_additional_fields(); $address_fields_keys = $this->additional_fields_controller->get_address_fields_keys(); $address_fields = array_filter( $fields, - function( $key ) use ( $address_fields_keys ) { + function ( $key ) use ( $address_fields_keys ) { return in_array( $key, $address_fields_keys, true ); }, ARRAY_FILTER_USE_KEY @@ -570,7 +562,7 @@ public function validate_selected_shipping_methods( $needs_shipping, $chosen_shi if ( false === $chosen_shipping_method || ! is_string( $chosen_shipping_method ) || - ! in_array( current( explode( ':', $chosen_shipping_method ) ), $valid_methods, true ) + ! ArrayUtils::string_contains_array( $chosen_shipping_method, $valid_methods ) ) { throw $exception; } @@ -759,9 +751,6 @@ protected function update_addresses_from_cart( \WC_Order $order ) { 'shipping_phone' => wc()->customer->get_shipping_phone(), ) ); - $customer_fields = $this->additional_fields_controller->get_all_fields_from_customer( wc()->customer ); - foreach ( $customer_fields as $key => $value ) { - $this->additional_fields_controller->persist_field_for_order( $key, $value, $order, false ); - } + $this->additional_fields_controller->sync_order_additional_fields_with_customer( $order, wc()->customer ); } } diff --git a/plugins/woocommerce/src/Utilities/OrderUtil.php b/plugins/woocommerce/src/Utilities/OrderUtil.php index b8a0fa4d8e7f9..8e94cbb8a9c0c 100644 --- a/plugins/woocommerce/src/Utilities/OrderUtil.php +++ b/plugins/woocommerce/src/Utilities/OrderUtil.php @@ -196,8 +196,8 @@ public static function get_table_for_order_meta() { public static function get_count_for_type( $order_type ) { global $wpdb; - $cache_key = 'order-count-' . $order_type; - $count_per_status = wp_cache_get( $cache_key, 'orders' ); + $cache_key = \WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'order-count-' . $order_type; + $count_per_status = wp_cache_get( $cache_key, 'counts' ); if ( false === $count_per_status ) { if ( self::custom_orders_table_usage_is_enabled() ) { @@ -222,7 +222,7 @@ public static function get_count_for_type( $order_type ) { $count_per_status ); - wp_cache_set( $cache_key, $count_per_status, 'orders' ); + wp_cache_set( $cache_key, $count_per_status, 'counts' ); } return $count_per_status; diff --git a/plugins/woocommerce/src/Utilities/PluginUtil.php b/plugins/woocommerce/src/Utilities/PluginUtil.php index 59a2f155db4c5..2d9cde37624ef 100644 --- a/plugins/woocommerce/src/Utilities/PluginUtil.php +++ b/plugins/woocommerce/src/Utilities/PluginUtil.php @@ -6,6 +6,7 @@ namespace Automattic\WooCommerce\Utilities; use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; +use Automattic\WooCommerce\Internal\Utilities\PluginInstaller; use Automattic\WooCommerce\Proxies\LegacyProxy; /** @@ -182,7 +183,10 @@ private function handle_plugin_de_activation(): void { } /** - * Util function to generate warning string for incompatible features based on active plugins. + * Utility method to generate warning string for incompatible features based on active plugins. + * + * Additionally, this method will manually print a warning message on the HPOS feature if both + * the Legacy REST API and HPOS are active. * * @param string $feature_id Feature id. * @param array $plugin_feature_info Array of plugin feature info. See FeaturesControllers->get_compatible_plugins_for_feature() for details. @@ -195,20 +199,55 @@ public function generate_incompatible_plugin_feature_warning( string $feature_id $incompatibles = array_filter( $incompatibles, 'is_plugin_active' ); $incompatibles = array_values( array_diff( $incompatibles, $this->get_plugins_excluded_from_compatibility_ui() ) ); $incompatible_count = count( $incompatibles ); + + $feature_warnings = array(); + if ( 'custom_order_tables' === $feature_id && 'yes' === get_option( 'woocommerce_api_enabled' ) ) { + if ( is_plugin_active( 'woocommerce-legacy-rest-api/woocommerce-legacy-rest-api.php' ) ) { + $legacy_api_and_hpos_incompatibility_warning_text = + sprintf( + // translators: %s is a URL. + __( '⚠ The Legacy REST API plugin is installed and active on this site. Please be aware that the WooCommerce Legacy REST API is not compatible with HPOS.', 'woocommerce' ), + 'https://wordpress.org/plugins/woocommerce-legacy-rest-api/' + ); + } else { + $legacy_api_and_hpos_incompatibility_warning_text = + sprintf( + // translators: %s is a URL. + __( '⚠ The Legacy REST API is active on this site. Please be aware that the WooCommerce Legacy REST API is not compatible with HPOS.', 'woocommerce' ), + admin_url( 'admin.php?page=wc-settings&tab=advanced§ion=legacy_api' ) + ); + } + + /** + * Filter to modify the warning text that appears in the HPOS section of the features settings page + * when both the Legacy REST API is active (via WooCommerce core or via the Legacy REST API plugin) + * and the orders table is in use as the primary data store for orders. + * + * @param string $legacy_api_and_hpos_incompatibility_warning_text Original warning text. + * @returns string|null Actual warning text to use, or null to suppress the warning. + * + * @since 8.9.0 + */ + $legacy_api_and_hpos_incompatibility_warning_text = apply_filters( 'woocommerce_legacy_api_and_hpos_incompatibility_warning_text', $legacy_api_and_hpos_incompatibility_warning_text ); + + if ( ! is_null( $legacy_api_and_hpos_incompatibility_warning_text ) ) { + $feature_warnings[] = $legacy_api_and_hpos_incompatibility_warning_text . "\n"; + } + } + if ( $incompatible_count > 0 ) { if ( 1 === $incompatible_count ) { /* translators: %s = printable plugin name */ - $feature_warning = sprintf( __( '⚠ 1 Incompatible plugin detected (%s).', 'woocommerce' ), $this->get_plugin_name( $incompatibles[0] ) ); + $feature_warnings[] = sprintf( __( '⚠ 1 Incompatible plugin detected (%s).', 'woocommerce' ), $this->get_plugin_name( $incompatibles[0] ) ); } elseif ( 2 === $incompatible_count ) { - $feature_warning = sprintf( + $feature_warnings[] = sprintf( /* translators: %1\$s, %2\$s = printable plugin names */ __( '⚠ 2 Incompatible plugins detected (%1$s and %2$s).', 'woocommerce' ), $this->get_plugin_name( $incompatibles[0] ), $this->get_plugin_name( $incompatibles[1] ) ); } else { - - $feature_warning = sprintf( + $feature_warnings[] = sprintf( /* translators: %1\$s, %2\$s = printable plugin names, %3\$d = plugins count */ _n( '⚠ Incompatible plugins detected (%1$s, %2$s and %3$d other).', @@ -229,18 +268,15 @@ public function generate_incompatible_plugin_feature_warning( string $feature_id ), admin_url( 'plugins.php' ) ); - $extra_desc_tip = '
' . sprintf( + $feature_warnings[] = sprintf( /* translators: %1$s opening link tag %2$s closing link tag. */ __( '%1$sView and manage%2$s', 'woocommerce' ), '', '' ); - - $feature_warning .= $extra_desc_tip; - } - return $feature_warning; + return str_replace( "\n", '
', implode( "\n", $feature_warnings ) ); } /** diff --git a/plugins/woocommerce/templates/loop/add-to-cart.php b/plugins/woocommerce/templates/loop/add-to-cart.php index b0aec5d4c7eeb..ad3f086e2a98a 100644 --- a/plugins/woocommerce/templates/loop/add-to-cart.php +++ b/plugins/woocommerce/templates/loop/add-to-cart.php @@ -12,7 +12,7 @@ * * @see https://woocommerce.com/document/template-structure/ * @package WooCommerce\Templates - * @version 3.3.0 + * @version 9.0.0 */ if ( ! defined( 'ABSPATH' ) ) { @@ -24,8 +24,9 @@ echo apply_filters( 'woocommerce_loop_add_to_cart_link', // WPCS: XSS ok. sprintf( - '%s', + '%s', esc_url( $product->add_to_cart_url() ), + esc_attr( $product->get_id() ), esc_attr( isset( $args['quantity'] ) ? $args['quantity'] : 1 ), esc_attr( isset( $args['class'] ) ? $args['class'] : 'button' ), isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '', @@ -34,3 +35,7 @@ $product, $args ); +?> + + + diff --git a/plugins/woocommerce/templates/myaccount/payment-methods.php b/plugins/woocommerce/templates/myaccount/payment-methods.php index f602ced983748..46b629e70b0b0 100644 --- a/plugins/woocommerce/templates/myaccount/payment-methods.php +++ b/plugins/woocommerce/templates/myaccount/payment-methods.php @@ -14,7 +14,7 @@ * * @see https://woocommerce.com/document/template-structure/ * @package WooCommerce\Templates - * @version 7.8.0 + * @version 8.9.0 */ defined( 'ABSPATH' ) || exit; diff --git a/plugins/woocommerce/templates/templates/archive-product.html b/plugins/woocommerce/templates/templates/archive-product.html index 54df5c16c351b..de40051381a0b 100644 --- a/plugins/woocommerce/templates/templates/archive-product.html +++ b/plugins/woocommerce/templates/templates/archive-product.html @@ -1,5 +1,5 @@ - -
+ +
diff --git a/plugins/woocommerce/templates/templates/blockified/archive-product.html b/plugins/woocommerce/templates/templates/blockified/archive-product.html index 8c1e0029832b4..0212b71045e51 100644 --- a/plugins/woocommerce/templates/templates/blockified/archive-product.html +++ b/plugins/woocommerce/templates/templates/blockified/archive-product.html @@ -1,7 +1,7 @@ - -
+ +
@@ -36,7 +36,7 @@
-
+ diff --git a/plugins/woocommerce/templates/templates/blockified/coming-soon-entire-site.html b/plugins/woocommerce/templates/templates/blockified/coming-soon-entire-site.html deleted file mode 100644 index c7a616315f6f5..0000000000000 --- a/plugins/woocommerce/templates/templates/blockified/coming-soon-entire-site.html +++ /dev/null @@ -1,53 +0,0 @@ - -
-
-
- - -
-
- - - -
- - - -
-
-
- - - - - - -
-
- -
-
- - - diff --git a/plugins/woocommerce/templates/templates/blockified/coming-soon-store-only.html b/plugins/woocommerce/templates/templates/blockified/coming-soon-store-only.html deleted file mode 100644 index 781eac1c2d21e..0000000000000 --- a/plugins/woocommerce/templates/templates/blockified/coming-soon-store-only.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/plugins/woocommerce/templates/templates/blockified/coming-soon.html b/plugins/woocommerce/templates/templates/blockified/coming-soon.html new file mode 100644 index 0000000000000..7f96aa691c393 --- /dev/null +++ b/plugins/woocommerce/templates/templates/blockified/coming-soon.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/woocommerce/templates/templates/blockified/product-search-results.html b/plugins/woocommerce/templates/templates/blockified/product-search-results.html index fb19ae3f2b380..5ddb970e2235f 100644 --- a/plugins/woocommerce/templates/templates/blockified/product-search-results.html +++ b/plugins/woocommerce/templates/templates/blockified/product-search-results.html @@ -1,7 +1,7 @@ - -
+ +
@@ -35,7 +35,7 @@
-
+ diff --git a/plugins/woocommerce/templates/templates/blockified/single-product.html b/plugins/woocommerce/templates/templates/blockified/single-product.html index 9eb5414fd075a..72e3458696ca6 100644 --- a/plugins/woocommerce/templates/templates/blockified/single-product.html +++ b/plugins/woocommerce/templates/templates/blockified/single-product.html @@ -1,7 +1,7 @@ - -
+ +
@@ -46,7 +46,7 @@ -
+ diff --git a/plugins/woocommerce/templates/templates/blockified/taxonomy-product_attribute.html b/plugins/woocommerce/templates/templates/blockified/taxonomy-product_attribute.html index ef0b836cca613..0b129ff167768 100644 --- a/plugins/woocommerce/templates/templates/blockified/taxonomy-product_attribute.html +++ b/plugins/woocommerce/templates/templates/blockified/taxonomy-product_attribute.html @@ -1,7 +1,7 @@ - -
+ +
@@ -43,7 +43,7 @@
-
+ diff --git a/plugins/woocommerce/templates/templates/blockified/taxonomy-product_cat.html b/plugins/woocommerce/templates/templates/blockified/taxonomy-product_cat.html index ef0b836cca613..0b129ff167768 100644 --- a/plugins/woocommerce/templates/templates/blockified/taxonomy-product_cat.html +++ b/plugins/woocommerce/templates/templates/blockified/taxonomy-product_cat.html @@ -1,7 +1,7 @@ - -
+ +
@@ -43,7 +43,7 @@
-
+ diff --git a/plugins/woocommerce/templates/templates/blockified/taxonomy-product_tag.html b/plugins/woocommerce/templates/templates/blockified/taxonomy-product_tag.html index 102d55102ad9b..c20c52897d9e1 100644 --- a/plugins/woocommerce/templates/templates/blockified/taxonomy-product_tag.html +++ b/plugins/woocommerce/templates/templates/blockified/taxonomy-product_tag.html @@ -1,7 +1,7 @@ - -
+ +
@@ -43,4 +43,4 @@
-
+ diff --git a/plugins/woocommerce/templates/templates/coming-soon-entire-site.html b/plugins/woocommerce/templates/templates/coming-soon-entire-site.html deleted file mode 100644 index c7a616315f6f5..0000000000000 --- a/plugins/woocommerce/templates/templates/coming-soon-entire-site.html +++ /dev/null @@ -1,53 +0,0 @@ - -
-
-
- - -
-
- - - -
- - - -
-
-
- - - - - - -
-
- -
-
- - - diff --git a/plugins/woocommerce/templates/templates/coming-soon-store-only.html b/plugins/woocommerce/templates/templates/coming-soon-store-only.html deleted file mode 100644 index 781eac1c2d21e..0000000000000 --- a/plugins/woocommerce/templates/templates/coming-soon-store-only.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/plugins/woocommerce/templates/templates/coming-soon.html b/plugins/woocommerce/templates/templates/coming-soon.html new file mode 100644 index 0000000000000..7f96aa691c393 --- /dev/null +++ b/plugins/woocommerce/templates/templates/coming-soon.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/woocommerce/templates/templates/product-search-results.html b/plugins/woocommerce/templates/templates/product-search-results.html index 7f5a74c59f690..db4d5cd271323 100644 --- a/plugins/woocommerce/templates/templates/product-search-results.html +++ b/plugins/woocommerce/templates/templates/product-search-results.html @@ -1,7 +1,7 @@ - -
+ +
-
+ diff --git a/plugins/woocommerce/templates/templates/single-product.html b/plugins/woocommerce/templates/templates/single-product.html index aa382cef59485..ed2dafba92703 100644 --- a/plugins/woocommerce/templates/templates/single-product.html +++ b/plugins/woocommerce/templates/templates/single-product.html @@ -1,5 +1,5 @@ - -
+ +
diff --git a/plugins/woocommerce/templates/templates/taxonomy-product_attribute.html b/plugins/woocommerce/templates/templates/taxonomy-product_attribute.html index 7f3f66cd92d3d..b05b1d54e7bb6 100644 --- a/plugins/woocommerce/templates/templates/taxonomy-product_attribute.html +++ b/plugins/woocommerce/templates/templates/taxonomy-product_attribute.html @@ -1,5 +1,5 @@ - -
+ +
diff --git a/plugins/woocommerce/templates/templates/taxonomy-product_cat.html b/plugins/woocommerce/templates/templates/taxonomy-product_cat.html index 590675cb75e08..1773a59def45e 100644 --- a/plugins/woocommerce/templates/templates/taxonomy-product_cat.html +++ b/plugins/woocommerce/templates/templates/taxonomy-product_cat.html @@ -1,5 +1,5 @@ - -
+ +
diff --git a/plugins/woocommerce/templates/templates/taxonomy-product_tag.html b/plugins/woocommerce/templates/templates/taxonomy-product_tag.html index bf2fbf90f95e0..f3c5c3de3aae4 100644 --- a/plugins/woocommerce/templates/templates/taxonomy-product_tag.html +++ b/plugins/woocommerce/templates/templates/taxonomy-product_tag.html @@ -1,5 +1,5 @@ - -
+ +
diff --git a/plugins/woocommerce/tests/e2e-pw/.eslintrc.js b/plugins/woocommerce/tests/e2e-pw/.eslintrc.js index 7d96e246f8957..fe1e0335ed53d 100644 --- a/plugins/woocommerce/tests/e2e-pw/.eslintrc.js +++ b/plugins/woocommerce/tests/e2e-pw/.eslintrc.js @@ -6,5 +6,7 @@ module.exports = { 'jest/no-test-callback': 'off', 'jest/no-disabled-tests': 'off', 'jest/valid-expect': 'off', + 'jest/expect-expect': 'off', + 'testing-library/await-async-utils': 'off', }, }; diff --git a/plugins/woocommerce/tests/e2e-pw/.gitignore b/plugins/woocommerce/tests/e2e-pw/.gitignore new file mode 100644 index 0000000000000..cfc719aabe4d3 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/.gitignore @@ -0,0 +1,3 @@ +/.state +/test-results + diff --git a/plugins/woocommerce/tests/e2e-pw/bin/install-plugin.sh b/plugins/woocommerce/tests/e2e-pw/bin/install-plugin.sh new file mode 100755 index 0000000000000..51caa4c3c38c9 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/bin/install-plugin.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -eo pipefail + +if [[ -z "$PLUGIN_REPOSITORY" ]]; then + echo "::error::PLUGIN_REPOSITORY must be set" + exit 1 +fi + +if [[ -z "$PLUGIN_NAME" ]]; then + echo "::error::PLUGIN_NAME must be set" + exit 1 +fi + +if [[ -z "$PLUGIN_SLUG" ]]; then + echo "::error::PLUGIN_SLUG must be set" + exit 1 +fi + + +echo "Installing $PLUGIN_NAME from $PLUGIN_REPOSITORY" + +echo "Uninstalling plugin if it's already installed..." +pnpm wp-env run tests-cli wp plugin uninstall "$PLUGIN_SLUG" --deactivate || true + +echo "Downloading plugin..." +download_url=$(curl -s "https://api.github.com/repos/$PLUGIN_REPOSITORY/releases/latest" | grep browser_download_url | cut -d '"' -f 4) +pnpm wp-env run tests-cli wp plugin install "$download_url" --activate + +pnpm wp-env run tests-cli wp plugin list +pnpm wp-env run tests-cli wp plugin is-active "$PLUGIN_SLUG" || ( echo "Plugin \"$PLUGIN_SLUG\" is not active!" && exit 1 ) diff --git a/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh b/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh index 5e7ff9c1aaefe..b32297c3ed0a6 100755 --- a/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh +++ b/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh @@ -1,5 +1,13 @@ #!/usr/bin/env bash +DISABLE_HPOS="${DISABLE_HPOS:-0}" + +echo -e "DISABLE_HPOS: $DISABLE_HPOS" +if [ $DISABLE_HPOS == 1 ]; then + echo -e 'Disabling HPOS\n' + wp-env run tests-cli wp option update woocommerce_custom_orders_table_enabled 'no' +fi + ENABLE_TRACKING="${ENABLE_TRACKING:-0}" echo -e 'Activate default theme \n' @@ -38,6 +46,8 @@ if [ $ENABLE_TRACKING == 1 ]; then wp-env run tests-cli wp option update woocommerce_allow_tracking 'yes' fi +echo -e 'Disabling coming soon option\n' +wp-env run tests-cli wp option update woocommerce_coming_soon 'no' + echo -e 'Upload test images \n' wp-env run tests-cli wp media import './test-data/images/image-01.png' './test-data/images/image-02.png' './test-data/images/image-03.png' - diff --git a/plugins/woocommerce/tests/e2e-pw/fixtures/fixtures.js b/plugins/woocommerce/tests/e2e-pw/fixtures/fixtures.js index 5d8448b03f819..a0af6568d0389 100644 --- a/plugins/woocommerce/tests/e2e-pw/fixtures/fixtures.js +++ b/plugins/woocommerce/tests/e2e-pw/fixtures/fixtures.js @@ -1,6 +1,7 @@ const base = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const { admin } = require( '../test-data/data' ); +const { random } = require( '../utils/helpers' ); exports.test = base.test.extend( { api: async ( { baseURL }, use ) => { @@ -43,6 +44,35 @@ exports.test = base.test.extend( { await use( wpApi ); }, + + testPageTitlePrefix: [ '', { option: true } ], + + testPage: async ( { wpApi, testPageTitlePrefix }, use ) => { + const pageTitle = `${ testPageTitlePrefix } Page ${ random() }`; + const pageSlug = pageTitle.replace( / /gi, '-' ).toLowerCase(); + + await use( { title: pageTitle, slug: pageSlug } ); + + // Cleanup + const pages = await wpApi.get( + `/wp-json/wp/v2/pages?slug=${ pageSlug }`, + { + data: { + _fields: [ 'id' ], + }, + failOnStatusCode: false, + } + ); + + for ( const page of await pages.json() ) { + console.log( `Deleting page ${ page.id }` ); + await wpApi.delete( `/wp-json/wp/v2/pages/${ page.id }`, { + data: { + force: true, + }, + } ); + } + }, } ); exports.expect = base.expect; diff --git a/plugins/woocommerce/tests/e2e-pw/global-setup.js b/plugins/woocommerce/tests/e2e-pw/global-setup.js index 9477073587926..bd5dd329d9801 100644 --- a/plugins/woocommerce/tests/e2e-pw/global-setup.js +++ b/plugins/woocommerce/tests/e2e-pw/global-setup.js @@ -189,14 +189,17 @@ module.exports = async ( config ) => { // (if a value for ENABLE_HPOS was set) // This was always being set to 'yes' after login in wp-env so this step ensures the // correct value is set before we begin our tests + console.log( `ENABLE_HPOS: ${ ENABLE_HPOS }` ); + + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + if ( ENABLE_HPOS ) { const hposSettingRetries = 5; - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); const value = ENABLE_HPOS === '0' ? 'no' : 'yes'; @@ -236,6 +239,12 @@ module.exports = async ( config ) => { } } + const response = await api.get( + 'settings/advanced/woocommerce_custom_orders_table_enabled' + ); + + console.log( `HPOS configuration ${ response.data.value }` ); + await site.useCartCheckoutShortcodes( baseURL, userAgent, admin ); await adminContext.close(); diff --git a/plugins/woocommerce/tests/e2e-pw/global-teardown.js b/plugins/woocommerce/tests/e2e-pw/global-teardown.js index eaf8ea93cc0e1..5e18f22e49383 100644 --- a/plugins/woocommerce/tests/e2e-pw/global-teardown.js +++ b/plugins/woocommerce/tests/e2e-pw/global-teardown.js @@ -1,4 +1,4 @@ -const { chromium } = require( '@playwright/test' ); +const { chromium, expect } = require( '@playwright/test' ); const { admin } = require( './test-data/data' ); module.exports = async ( config ) => { @@ -95,8 +95,5 @@ module.exports = async ( config ) => { } } - if ( ! consumerTokenCleared ) { - console.error( 'Could not clear consumer token.' ); - process.exit( 1 ); - } + await expect( consumerTokenCleared ).toBe( true ); }; diff --git a/plugins/woocommerce/tests/e2e-pw/playwright.config.js b/plugins/woocommerce/tests/e2e-pw/playwright.config.js index 70beb77498ad4..36b6b128f90aa 100644 --- a/plugins/woocommerce/tests/e2e-pw/playwright.config.js +++ b/plugins/woocommerce/tests/e2e-pw/playwright.config.js @@ -11,12 +11,43 @@ const { REPEAT_EACH, } = process.env; +const reporter = [ + [ 'list' ], + [ + 'allure-playwright', + { + outputFolder: + ALLURE_RESULTS_DIR ?? + './tests/e2e-pw/test-results/allure-results', + detail: true, + suiteTitle: true, + }, + ], + [ + 'json', + { outputFile: `./test-results/test-results-${ Date.now() }.json` }, + ], +]; + +if ( process.env.CI ) { + reporter.push( [ 'github' ] ); +} else { + reporter.push( [ + 'html', + { + outputFolder: + PLAYWRIGHT_HTML_REPORT ?? './test-results/playwright-report', + open: 'on-failure', + }, + ] ); +} + const config = { timeout: DEFAULT_TIMEOUT_OVERRIDE ? Number( DEFAULT_TIMEOUT_OVERRIDE ) : 120 * 1000, expect: { timeout: 20 * 1000 }, - outputDir: './test-results/report', + outputDir: './test-results/results-data', globalSetup: require.resolve( './global-setup' ), globalTeardown: require.resolve( './global-teardown' ), testDir: 'tests', @@ -24,35 +55,12 @@ const config = { repeatEach: REPEAT_EACH ? Number( REPEAT_EACH ) : 1, workers: 1, reportSlowTests: { max: 5, threshold: 30 * 1000 }, // 30 seconds threshold - reporter: [ - [ 'list' ], - [ - 'html', - { - outputFolder: - PLAYWRIGHT_HTML_REPORT ?? - './test-results/playwright-report', - open: CI ? 'never' : 'always', - }, - ], - [ - 'allure-playwright', - { - outputFolder: - ALLURE_RESULTS_DIR ?? - './tests/e2e-pw/test-results/allure-results', - detail: true, - suiteTitle: true, - }, - ], - [ 'json', { outputFile: './test-results/test-results.json' } ], - [ 'github' ], - ], + reporter, maxFailures: E2E_MAX_FAILURES ? Number( E2E_MAX_FAILURES ) : 0, use: { baseURL: BASE_URL ?? 'http://localhost:8086', screenshot: { mode: 'only-on-failure', fullPage: true }, - stateDir: 'tests/e2e-pw/test-results/storage/', + stateDir: 'tests/e2e-pw/.state/', trace: 'retain-on-failure', video: 'retain-on-failure', viewport: { width: 1280, height: 720 }, diff --git a/plugins/woocommerce/tests/e2e-pw/tests/admin-analytics/analytics-data.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/admin-analytics/analytics-data.spec.js index db90d4f048f22..fe68990fbbf59 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/admin-analytics/analytics-data.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/admin-analytics/analytics-data.spec.js @@ -528,8 +528,8 @@ test.describe( 'Analytics-related tests', () => { await page.getByRole( 'button', { name: 'All orders' } ).click(); await page.getByText( 'Advanced filters' ).click(); - await page.getByRole( 'button', { name: 'Add a Filter' } ).click(); - await page.getByRole( 'button', { name: 'Order Status' } ).click(); + await page.getByRole( 'button', { name: 'Add a filter' } ).click(); + await page.getByRole( 'button', { name: 'Order status' } ).click(); await page .getByLabel( 'Select an order status filter match' ) .selectOption( 'Is' ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/basic.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/basic.spec.js index 18026725e435b..255a23d738d36 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/basic.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/basic.spec.js @@ -9,6 +9,13 @@ test( 'Load the home page', async ( { page } ) => { .getByRole( 'link', { name: 'WooCommerce Core E2E Test' } ) .count() ).toBeGreaterThan( 0 ); + await expect( + page.getByText( 'Proudly powered by WordPress' ) + ).toBeVisible(); + expect( await page.title() ).toBe( 'WooCommerce Core E2E Test Suite' ); + await expect( + page.getByRole( 'link', { name: 'WordPress' } ) + ).toBeVisible(); } ); test( 'Load wp-admin as admin', async ( { page } ) => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/assembler.page.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/assembler.page.js index 87fe1ecc5718e..941df271ce0ce 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/assembler.page.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/assembler.page.js @@ -16,7 +16,7 @@ export class AssemblerPage { '.cys-fullscreen-iframe[style="opacity: 1;"]' ); - await frame.getByRole( 'button', { name: 'Done' } ).waitFor(); + await frame.getByRole( 'button', { name: 'Save' } ).waitFor(); } /** diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/color-picker.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/color-picker.spec.js index 26f7f6e0fb2b0..4daf16b1fd885 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/color-picker.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/color-picker.spec.js @@ -298,6 +298,8 @@ test.describe( 'Assembler -> Color Pickers', () => { await colorPicker.click(); + await assembler.locator( '[aria-label="Back"]' ).click(); + const saveButton = assembler.getByText( 'Save' ); const waitResponse = page.waitForResponse( @@ -382,51 +384,6 @@ test.describe( 'Assembler -> Color Pickers', () => { await expect( colorPicker ).toHaveClass( /is-active/ ); } ); - test( 'Picking a color should activate the save button', async ( { - assemblerPageObject, - } ) => { - const assembler = await assemblerPageObject.getAssembler(); - const colorPicker = assembler - .locator( - '.woocommerce-customize-store_global-styles-variations_item' - ) - .nth( 2 ); - - await colorPicker.click(); - - const saveButton = assembler.getByText( 'Save' ); - - await expect( saveButton ).toBeEnabled(); - } ); - - test( 'The Done button should be visible after clicking save', async ( { - assemblerPageObject, - page, - } ) => { - const assembler = await assemblerPageObject.getAssembler(); - const colorPicker = assembler - .locator( - '.woocommerce-customize-store_global-styles-variations_item' - ) - .nth( 2 ); - - await colorPicker.click(); - - const saveButton = assembler.getByText( 'Save' ); - - const waitResponse = page.waitForResponse( - ( response ) => - response.url().includes( 'wp-json/wp/v2/global-styles' ) && - response.status() === 200 - ); - - await saveButton.click(); - - await waitResponse; - - await expect( assembler.getByText( 'Done' ) ).toBeEnabled(); - } ); - test( 'Selected color palette should be applied on the frontend', async ( { assemblerPageObject, page, @@ -442,6 +399,8 @@ test.describe( 'Assembler -> Color Pickers', () => { await colorPicker.click(); + await assembler.locator( '[aria-label="Back"]' ).click(); + const saveButton = assembler.getByText( 'Save' ); const waitResponse = page.waitForResponse( @@ -512,4 +471,58 @@ test.describe( 'Assembler -> Color Pickers', () => { ).toBe( true ); } } ); + + test( 'Create "your own" pickers should be visible', async ( { + assemblerPageObject, + }, testInfo ) => { + testInfo.snapshotSuffix = ''; + const assembler = await assemblerPageObject.getAssembler(); + const colorPicker = assembler.getByText( 'Create your own' ); + + await colorPicker.click(); + + const mapTypeFeatures = { + background: [ 'solid', 'gradient' ], + text: [], + heading: [ 'text', 'background', 'gradient' ], + button: [ 'text', 'background', 'gradient' ], + link: [ 'default', 'hover' ], + captions: [], + }; + + const mapFeatureSelectors = { + solid: '.components-color-palette__custom-color-button', + text: '.components-color-palette__custom-color-button', + background: '.components-color-palette__custom-color-button', + default: '.components-color-palette__custom-color-button', + hover: '.components-color-palette__custom-color-button', + gradient: + '.components-custom-gradient-picker__gradient-bar-background', + }; + + for ( const type of Object.keys( mapTypeFeatures ) ) { + await assembler + .locator( + '.woocommerce-customize-store__color-panel-container' + ) + .getByText( type ) + .click(); + + for ( const feature of mapTypeFeatures[ type ] ) { + const container = assembler.locator( + '.block-editor-panel-color-gradient-settings__dropdown-content' + ); + await container + .getByRole( 'tab', { + name: feature, + } ) + .click(); + + const selector = mapFeatureSelectors[ feature ]; + const featureSelector = container.locator( selector ); + + await expect( featureSelector ).toBeVisible(); + } + } + } ); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/font-picker.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/font-picker.spec.js index dbe1909a17fa7..6f6bb804a9a1d 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/font-picker.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/font-picker.spec.js @@ -154,47 +154,6 @@ test.describe( 'Assembler -> Font Picker', () => { await expect( fontPicker ).toHaveClass( /is-active/ ); } ); - test( 'Picking a font should activate the save button', async ( { - pageObject, - } ) => { - const assembler = await pageObject.getAssembler(); - const fontPicker = assembler.locator( - '.woocommerce-customize-store_global-styles-variations_item:not(.is-active)' - ); - - await fontPicker.click(); - - const saveButton = assembler.getByText( 'Save' ); - - await expect( saveButton ).toBeEnabled(); - } ); - - test( 'The Done button should be visible after clicking save', async ( { - pageObject, - page, - } ) => { - const assembler = await pageObject.getAssembler(); - const fontPicker = assembler.locator( - '.woocommerce-customize-store_global-styles-variations_item:not(.is-active)' - ); - - await fontPicker.click(); - - const saveButton = assembler.getByText( 'Save' ); - - const waitResponse = page.waitForResponse( - ( response ) => - response.url().includes( 'wp-json/wp/v2/global-styles' ) && - response.status() === 200 - ); - - await saveButton.click(); - - await waitResponse; - - await expect( assembler.getByText( 'Done' ) ).toBeEnabled(); - } ); - test( 'Selected font palette should be applied on the frontend', async ( { pageObject, page, @@ -216,6 +175,8 @@ test.describe( 'Assembler -> Font Picker', () => { .split( '+' ) .map( ( e ) => e.trim() ); + await assembler.locator( '[aria-label="Back"]' ).click(); + const saveButton = assembler.getByText( 'Save' ); const waitResponse = page.waitForResponse( diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/footer.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/footer.spec.js index 727140a5453ba..d2627239c50bf 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/footer.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/footer.spec.js @@ -3,6 +3,14 @@ const { AssemblerPage } = require( './assembler.page' ); const { activateTheme, DEFAULT_THEME } = require( '../../../utils/themes' ); const { setOption } = require( '../../../utils/options' ); +const extractFooterClass = ( footerPickerClass ) => { + const regex = /\bwc-blocks-pattern-footer\S*/; + + const match = footerPickerClass.match( regex ); + + return match ? match[ 0 ] : null; +}; + const test = base.extend( { assemblerPage: async ( { page }, use ) => { const assemblerPage = new AssemblerPage( { page } ); @@ -80,31 +88,6 @@ test.describe( 'Assembler -> Footers', () => { await expect( footer ).toHaveClass( /is-selected/ ); } ); - test( 'The Done button should be visible after clicking save', async ( { - assemblerPage, - page, - } ) => { - const assembler = await assemblerPage.getAssembler(); - const footer = assembler - .locator( '.block-editor-block-patterns-list__item' ) - .nth( 2 ); - - await footer.click(); - - const saveButton = assembler.getByText( 'Save' ); - const waitResponse = page.waitForResponse( - ( response ) => - response.url().includes( 'wp-json/wp/v2/template-parts' ) && - response.status() === 200 - ); - - await saveButton.click(); - - await waitResponse; - - await expect( assembler.getByText( 'Done' ) ).toBeEnabled(); - } ); - test( 'The selected footer should be applied on the frontend', async ( { assemblerPage, page, @@ -123,6 +106,8 @@ test.describe( 'Assembler -> Footers', () => { await footer.click(); + await assembler.locator( '[aria-label="Back"]' ).click(); + const saveButton = assembler.getByText( 'Save' ); const waitResponse = page.waitForResponse( @@ -160,7 +145,6 @@ test.describe( 'Assembler -> Footers', () => { .locator( '.block-editor-block-patterns-list__list-item' ) .all(); - let index = 0; for ( const footerPicker of footerPickers ) { await footerPicker.waitFor(); await footerPicker.click(); @@ -179,16 +163,6 @@ test.describe( 'Assembler -> Footers', () => { await expect( await footerPattern.getAttribute( 'class' ) ).toContain( expectedFooterClass ); - - index++; } } ); } ); - -const extractFooterClass = ( footerPickerClass ) => { - const regex = /\bwc-blocks-pattern-footer\S*/; - - const match = footerPickerClass.match( regex ); - - return match ? match[ 0 ] : null; -}; diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/header.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/header.spec.js index f60b1df50380a..e7f079e89a398 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/header.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/header.spec.js @@ -3,6 +3,14 @@ const { AssemblerPage } = require( './assembler.page' ); const { activateTheme, DEFAULT_THEME } = require( '../../../utils/themes' ); const { setOption } = require( '../../../utils/options' ); +const extractHeaderClass = ( headerPickerClass ) => { + const regex = /\bwc-blocks-pattern-header\S*/; + + const match = headerPickerClass.match( regex ); + + return match ? match[ 0 ] : null; +}; + const test = base.extend( { assemblerPage: async ( { page }, use ) => { const assemblerPage = new AssemblerPage( { page } ); @@ -80,31 +88,6 @@ test.describe( 'Assembler -> headers', () => { await expect( header ).toHaveClass( /is-selected/ ); } ); - test( 'The Done button should be visible after clicking save', async ( { - assemblerPage, - page, - } ) => { - const assembler = await assemblerPage.getAssembler(); - const header = assembler - .locator( '.block-editor-block-patterns-list__item' ) - .nth( 2 ); - - await header.click(); - - const saveButton = assembler.getByText( 'Save' ); - const waitResponse = page.waitForResponse( - ( response ) => - response.url().includes( 'wp-json/wp/v2/template-parts' ) && - response.status() === 200 - ); - - await saveButton.click(); - - await waitResponse; - - await expect( assembler.getByText( 'Done' ) ).toBeEnabled(); - } ); - test( 'The selected header should be applied on the frontend', async ( { assemblerPage, page, @@ -123,6 +106,8 @@ test.describe( 'Assembler -> headers', () => { await header.click(); + await assembler.locator( '[aria-label="Back"]' ).click(); + const saveButton = assembler.getByText( 'Save' ); const waitResponse = page.waitForResponse( @@ -159,7 +144,6 @@ test.describe( 'Assembler -> headers', () => { .locator( '.block-editor-block-patterns-list__list-item' ) .all(); - let index = 0; for ( const headerPicker of headerPickers ) { await headerPicker.waitFor(); await headerPicker.click(); @@ -178,16 +162,6 @@ test.describe( 'Assembler -> headers', () => { await expect( await headerPattern.getAttribute( 'class' ) ).toContain( expectedHeaderClass ); - - index++; } } ); } ); - -const extractHeaderClass = ( headerPickerClass ) => { - const regex = /\bwc-blocks-pattern-header\S*/; - - const match = headerPickerClass.match( regex ); - - return match ? match[ 0 ] : null; -}; diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/homepage.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/homepage.spec.js index 0a33766c72bee..f3757e2e6dd95 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/homepage.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/homepage.spec.js @@ -121,34 +121,6 @@ test.describe( 'Assembler -> Homepage', () => { } } ); - test( 'The Done button should be visible after clicking save', async ( { - pageObject, - page, - } ) => { - const assembler = await pageObject.getAssembler(); - const homepage = assembler - .locator( '.block-editor-block-patterns-list__item' ) - .nth( 2 ); - - await homepage.click(); - - const saveButton = assembler.getByText( 'Save' ); - const waitResponse = page.waitForResponse( - ( response ) => - response - .url() - .includes( - 'wp-json/wp/v2/templates/twentytwentyfour//home' - ) && response.status() === 200 - ); - - await saveButton.click(); - - await waitResponse; - - await expect( assembler.getByText( 'Done' ) ).toBeEnabled(); - } ); - test( 'Selected homepage should be applied on the frontend', async ( { pageObject, page, @@ -162,6 +134,8 @@ test.describe( 'Assembler -> Homepage', () => { await homepage.click(); + await assembler.locator( '[aria-label="Back"]' ).click(); + const saveButton = assembler.getByText( 'Save' ); const waitResponse = page.waitForResponse( diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/logo-picker/logo-picker.page.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/logo-picker/logo-picker.page.js index a13aae0098165..11863a1631c49 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/logo-picker/logo-picker.page.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/logo-picker/logo-picker.page.js @@ -67,6 +67,7 @@ export class LogoPickerPage { } async saveLogoSettings( assemblerLocator ) { + await assemblerLocator.locator( '[aria-label="Back"]' ).click(); await assemblerLocator.getByText( 'Save' ).click(); const waitForLogoResponse = this.page.waitForResponse( ( response ) => diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/logo-picker/logo-picker.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/logo-picker/logo-picker.spec.js index 74cf019d3108f..50c326115d851 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/logo-picker/logo-picker.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/assembler/logo-picker/logo-picker.spec.js @@ -120,12 +120,10 @@ test.describe( 'Assembler -> Logo Picker', () => { await expect( logoPickerPageObject.getLogoLocator( editor ) ).toBeVisible(); - await expect( assembler.getByText( 'Save' ) ).toBeEnabled(); await expect( imageWidth ).toBeVisible(); await expect( linkLogoToHomepage ).toBeVisible(); await expect( useAsSiteIcon ).toBeVisible(); - await assembler.getByText( 'Save' ).click(); } ); test( 'Changing the image width should update the site preview and the frontend', async ( { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/loading-screen/loading-screen.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/loading-screen/loading-screen.spec.js index c5a7d0a81f1f7..01b39d96911e9 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/loading-screen/loading-screen.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/loading-screen/loading-screen.spec.js @@ -123,7 +123,7 @@ test.describe( 'Assembler - Loading Page', () => { await pageObject.waitForLoadingScreenFinish(); const assembler = await pageObject.getAssembler(); - await assembler.getByRole( 'button', { name: 'Done' } ).click(); + await assembler.getByRole( 'button', { name: 'Save' } ).click(); await pageObject.setupSite( baseURL ); const requestToSetupStore = createRequestsToSetupStoreDictionary(); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/transitional.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/transitional.spec.js index a43dd9fda2bb2..f2c5a5cf838ac 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/customize-store/transitional.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/customize-store/transitional.spec.js @@ -69,7 +69,7 @@ test.describe( 'Store owner can view the Transitional page', () => { await expect( page.url() ).toBe( `${ baseURL }${ INTRO_URL }` ); } ); - test( 'Clicking on "Done" in the assembler should go to the transitional page', async ( { + test( 'Clicking on "Save" in the assembler should go to the transitional page', async ( { pageObject, baseURL, } ) => { @@ -77,7 +77,7 @@ test.describe( 'Store owner can view the Transitional page', () => { await pageObject.waitForLoadingScreenFinish(); const assembler = await pageObject.getAssembler(); - await assembler.getByRole( 'button', { name: 'Done' } ).click(); + await assembler.getByRole( 'button', { name: 'Save' } ).click(); await expect( assembler.locator( 'text=Your store looks great!' ) diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/command-palette.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/command-palette.spec.js index bc118047578be..2d425642ed8ac 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/command-palette.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/command-palette.spec.js @@ -9,7 +9,10 @@ const clickOnCommandPaletteOption = async ( { page, optionName } ) => { // Press `Ctrl` + `K` to open the command palette. await page.keyboard.press( cmdKeyCombo ); - await page.getByPlaceholder( 'Search for commands' ).fill( optionName ); + await page + .getByLabel( 'Command palette' ) + .locator( 'input' ) + .fill( optionName ); // Click on the relevant option. const option = page.getByRole( 'option', { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-cart-block.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-cart-block.spec.js index 726734ddf7595..a30e9d5aafbc8 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-cart-block.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-cart-block.spec.js @@ -1,66 +1,33 @@ -const { test, expect } = require( '@playwright/test' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); - -const transformedCartBlockTitle = `Transformed Cart ${ Date.now() }`; -const transformedCartBlockSlug = transformedCartBlockTitle - .replace( / /gi, '-' ) - .toLowerCase(); - -test.describe( 'Transform Classic Cart To Cart Block', () => { - test.use( { storageState: process.env.ADMINSTATE } ); - - test( 'can transform classic cart to cart block', async ( { page } ) => { - // go to create a new page - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - - await disableWelcomeModal( { page } ); - - // fill page title - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( transformedCartBlockTitle ); - - // add classic cart block - await page.getByRole( 'textbox', { name: 'Add title' } ).click(); - await page.getByLabel( 'Add block' ).click(); - await page - .getByPlaceholder( 'Search', { exact: true } ) - .fill( 'classic cart' ); - await page - .getByRole( 'option' ) - .filter( { hasText: 'Classic Cart' } ) - .click(); +const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); +const { + goToPageEditor, + fillPageTitle, + insertBlock, + transformIntoBlocks, + publishPage, +} = require( '../../utils/editor' ); + +baseTest.describe( 'Transform Classic Cart To Cart Block', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + testPageTitlePrefix: 'Transformed cart', + } ); - // transform into blocks - await expect( - page.locator( - '.wp-block-woocommerce-classic-shortcode__placeholder-copy' - ) - ).toBeVisible(); - await page - .getByRole( 'button' ) - .filter( { hasText: 'Transform into blocks' } ) - .click(); - await expect( page.getByLabel( 'Dismiss this notice' ) ).toContainText( - 'Classic shortcode transformed to blocks.' - ); + test( 'can transform classic cart to cart block', async ( { + page, + testPage, + } ) => { + await goToPageEditor( { page } ); - // save and publish the page - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ transformedCartBlockTitle } is now live.` ) - ).toBeVisible(); + await fillPageTitle( page, testPage.title ); + await insertBlock( page, 'Classic Cart' ); + await transformIntoBlocks( page ); + await publishPage( page, testPage.title ); // go to frontend to verify transformed cart block - await page.goto( transformedCartBlockSlug ); + await page.goto( testPage.slug ); await expect( - page.getByRole( 'heading', { name: transformedCartBlockTitle } ) + page.getByRole( 'heading', { name: testPage.title } ) ).toBeVisible(); await expect( page.getByRole( 'heading', { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-checkout-block.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-checkout-block.spec.js index 3f76c1e2678be..1bf283240830a 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-checkout-block.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-checkout-block.spec.js @@ -1,27 +1,25 @@ -const { test, expect } = require( '@playwright/test' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); -const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; - -const transformedCheckoutBlockTitle = `Transformed Checkout ${ Date.now() }`; -const transformedCheckoutBlockSlug = transformedCheckoutBlockTitle - .replace( / /gi, '-' ) - .toLowerCase(); +const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); +const { + goToPageEditor, + getCanvas, + fillPageTitle, + insertBlock, + transformIntoBlocks, + publishPage, +} = require( '../../utils/editor' ); const simpleProductName = 'Very Simple Product'; const singleProductPrice = '999.00'; let productId, shippingZoneId; -test.describe( 'Transform Classic Checkout To Checkout Block', () => { - test.use( { storageState: process.env.ADMINSTATE } ); +baseTest.describe( 'Transform Classic Checkout To Checkout Block', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + testPageTitlePrefix: 'Transformed checkout', + } ); - test.beforeAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.beforeAll( async ( { api } ) => { // enable COD await api.put( 'payment_gateways/cod', { enabled: true, @@ -49,13 +47,7 @@ test.describe( 'Transform Classic Checkout To Checkout Block', () => { } ); } ); - test.afterAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.afterAll( async ( { api } ) => { await api.delete( `products/${ productId }`, { force: true, } ); @@ -72,65 +64,23 @@ test.describe( 'Transform Classic Checkout To Checkout Block', () => { test( 'can transform classic checkout to checkout block', async ( { page, - baseURL, + api, + testPage, } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + await goToPageEditor( { page } ); - // go to create a new page - await page.goto( 'wp-admin/post-new.php?post_type=page' ); + await fillPageTitle( page, testPage.title ); + await insertBlock( page, 'Classic Checkout' ); + await transformIntoBlocks( page ); - await disableWelcomeModal( { page } ); + // When Gutenberg is active, the canvas is in an iframe + let canvas = await getCanvas( page ); - // fill page title - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( transformedCheckoutBlockTitle ); + // Activate the terms and conditions checkbox + await canvas.getByLabel( 'Block: Terms and Conditions' ).click(); + await page.getByLabel( 'Require checkbox' ).check(); - // add classic checkout block - await page.getByRole( 'textbox', { name: 'Add title' } ).click(); - await page.getByLabel( 'Add block' ).click(); - await page - .getByPlaceholder( 'Search', { exact: true } ) - .fill( 'classic checkout' ); - await page - .getByRole( 'option' ) - .filter( { hasText: 'Classic Checkout' } ) - .click(); - - // transform into blocks - await expect( - page.locator( - '.wp-block-woocommerce-classic-shortcode__placeholder-copy' - ) - ).toBeVisible(); - await page - .getByRole( 'button' ) - .filter( { hasText: 'Transform into blocks' } ) - .click(); - await expect( page.getByLabel( 'Dismiss this notice' ) ).toContainText( - 'Classic shortcode transformed to blocks.' - ); - - // set terms & conditions and privacy policy as mandatory option - await page.locator( '.wc-block-checkout__terms' ).click(); - await page.getByLabel( 'Require checkbox' ).click(); - - // save and publish the page - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ transformedCheckoutBlockTitle } is now live.` ) - ).toBeVisible(); + await publishPage( page, testPage.title ); // add additional payment option after page creation await api.put( 'payment_gateways/bacs', { @@ -138,17 +88,16 @@ test.describe( 'Transform Classic Checkout To Checkout Block', () => { } ); await page.reload(); - // verify that enabled payment options are in the block - await expect( - page.locator( - '#radio-control-wc-payment-method-options-bacs__label' - ) - ).toContainText( 'Direct bank transfer' ); + // Mandatory to wait for the editor content, to ensure the iframe is loaded (if Gutenberg is active) + await expect( page.getByLabel( 'Editor content' ) ).toBeVisible(); + + // Get the canvas again after the page reload + canvas = await getCanvas( page ); + await expect( - page.locator( - '#radio-control-wc-payment-method-options-cod__label' - ) - ).toContainText( 'Cash on delivery' ); + canvas.getByText( 'Direct bank transfer' ) + ).toBeVisible(); + await expect( canvas.getByText( 'Cash on delivery' ) ).toBeVisible(); // add additional shipping methods after page creation await api.post( `shipping/zones/${ shippingZoneId }/methods`, { @@ -172,9 +121,9 @@ test.describe( 'Transform Classic Checkout To Checkout Block', () => { // go to frontend to verify transformed checkout block // before that add product to cart to be able to visit checkout page await page.goto( `/cart/?add-to-cart=${ productId }` ); - await page.goto( transformedCheckoutBlockSlug ); + await page.goto( testPage.slug ); await expect( - page.getByRole( 'heading', { name: transformedCheckoutBlockTitle } ) + page.getByRole( 'heading', { name: testPage.title } ) ).toBeVisible(); await expect( page diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-page.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-page.spec.js index 0d40a9a03d403..d634eca46e2dc 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-page.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-page.spec.js @@ -1,63 +1,34 @@ -const { test, expect, request } = require( '@playwright/test' ); -const { admin } = require( '../../test-data/data' ); -const { goToPageEditor } = require( '../../utils/editor' ); - -const pageTitle = `Page-${ new Date().getTime().toString() }`; - -test.describe( 'Can create a new page', () => { - test.use( { storageState: process.env.ADMINSTATE } ); - - test.afterAll( async ( { baseURL } ) => { - const base64auth = Buffer.from( - `${ admin.username }:${ admin.password }` - ).toString( 'base64' ); - const wpApi = await request.newContext( { - baseURL: `${ baseURL }/wp-json/wp/v2/`, - extraHTTPHeaders: { - Authorization: `Basic ${ base64auth }`, - }, - } ); - - let response = await wpApi.get( `pages` ); - const allPages = await response.json(); - - await allPages.forEach( async ( page ) => { - if ( page.title.rendered === pageTitle ) { - response = await wpApi.delete( `pages/${ page.id }`, { - data: { - force: true, - }, - } ); - } - } ); +const { test: baseTest } = require( '../../fixtures/fixtures' ); +const { + goToPageEditor, + fillPageTitle, + getCanvas, + publishPage, +} = require( '../../utils/editor' ); + +baseTest.describe( 'Can create a new page', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, } ); - test( 'can create new page', async ( { page } ) => { + // eslint-disable-next-line playwright/expect-expect + test( 'can create new page', async ( { page, testPage } ) => { await goToPageEditor( { page } ); - await page - .getByRole( 'textbox', { name: 'Add Title' } ) - .fill( pageTitle ); + await fillPageTitle( page, testPage.title ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); + const canvas = await getCanvas( page ); - await page + await canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); + + await canvas .getByRole( 'document', { name: 'Empty block; start writing or type forward slash to choose a block', } ) .fill( 'Test Page' ); - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - - await expect( - page.getByText( `${ pageTitle } is now live.` ) - ).toBeVisible(); + await publishPage( page, testPage.title ); } ); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-post.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-post.spec.js index fc61a6552edb2..89108074e4ce2 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-post.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-post.spec.js @@ -1,6 +1,10 @@ const { test, expect, request } = require( '@playwright/test' ); const { admin } = require( '../../test-data/data' ); -const { goToPostEditor } = require( '../../utils/editor' ); +const { + goToPostEditor, + fillPageTitle, + getCanvas, +} = require( '../../utils/editor' ); const postTitle = `Post-${ new Date().getTime().toString() }`; @@ -36,13 +40,15 @@ test.describe( 'Can create a new post', () => { test( 'can create new post', async ( { page } ) => { await goToPostEditor( { page } ); - await page - .getByRole( 'textbox', { name: 'Add Title' } ) - .fill( postTitle ); + await fillPageTitle( page, postTitle ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); + const canvas = await getCanvas( page ); - await page + await canvas + .getByRole( 'button', { name: 'Add default block' } ) + .click(); + + await canvas .getByRole( 'document', { name: 'Empty block; start writing or type forward slash to choose a block', } ) diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-blocks.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-blocks.spec.js index 00c2d0c3b53a5..fc8f3a379a377 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-blocks.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-blocks.spec.js @@ -1,8 +1,11 @@ -const { test, expect } = require( '@playwright/test' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); -const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; - -const allWooBlocksPageTitle = `Insert All Woo Blocks ${ Date.now() }`; +const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); +const { + goToPageEditor, + fillPageTitle, + insertBlock, + getCanvas, + publishPage, +} = require( '../../utils/editor' ); const simpleProductName = 'Simplest Product'; const singleProductPrice = '555.00'; @@ -86,16 +89,13 @@ const blocks = [ let productId, shippingZoneId, productTagId, attributeId, productCategoryId; -test.describe( 'Insert All WooCommerce Blocks Into Page', () => { - test.use( { storageState: process.env.ADMINSTATE } ); +baseTest.describe( 'Add WooCommerce Blocks Into Page', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + testPageTitlePrefix: 'Woocommerce Blocks', + } ); - test.beforeAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.beforeAll( async ( { api } ) => { // add product attribute await api .post( 'products/attributes', { @@ -165,13 +165,7 @@ test.describe( 'Insert All WooCommerce Blocks Into Page', () => { } ); } ); - test.afterAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.afterAll( async ( { api } ) => { await api.delete( `products/${ productId }`, { force: true, } ); @@ -189,67 +183,49 @@ test.describe( 'Insert All WooCommerce Blocks Into Page', () => { } ); } ); - test( `can insert all WooCommerce blocks into page`, async ( { page } ) => { - // go to create a new page - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - - await disableWelcomeModal( { page } ); + test( `can insert all WooCommerce blocks into page`, async ( { + page, + testPage, + } ) => { + await goToPageEditor( { page } ); - // fill page title - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( allWooBlocksPageTitle ); + await fillPageTitle( page, testPage.title ); - // add all WC blocks and verify them as added into page for ( let i = 0; i < blocks.length; i++ ) { - // click title field for block inserter to show up - await page.getByRole( 'textbox', { name: 'Add title' } ).click(); - - // add block into page - await page.getByLabel( 'Add block' ).click(); - await page - .getByPlaceholder( 'Search', { exact: true } ) - .fill( blocks[ i ].name ); - await page - .getByRole( 'option', { name: blocks[ i ].name, exact: true } ) - .click(); - - if ( blocks[ i ].name === 'Reviews by Product' ) { - await page.getByLabel( simpleProductName ).check(); - await page - .getByRole( 'button', { name: 'Done', exact: true } ) - .click(); - } - - // verify added blocks into page - await expect( - page - .getByRole( 'document', { - name: `Block: ${ blocks[ i ].name }`, - exact: true, - } ) - .first() - ).toBeVisible(); + await test.step( `Insert ${ blocks[ i ].name } block`, async () => { + await insertBlock( page, blocks[ i ].name ); + + const canvas = await getCanvas( page ); + + // eslint-disable-next-line playwright/no-conditional-in-test + if ( blocks[ i ].name === 'Reviews by Product' ) { + await canvas.getByLabel( simpleProductName ).check(); + await canvas + .getByRole( 'button', { name: 'Done', exact: true } ) + .click(); + } + + // verify added blocks into page + await expect( + canvas + .getByRole( 'document', { + name: `Block: ${ blocks[ i ].name }`, + exact: true, + } ) + .first() + ).toBeVisible(); + } ); } - // save and publish the page - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ allWooBlocksPageTitle } is now live.` ) - ).toBeVisible(); + await publishPage( page, testPage.title ); // check all blocks inside the page after publishing // except the product price due to invisibility and false-positive + const canvas = await getCanvas( page ); for ( let i = 1; i < blocks.length; i++ ) { // verify added blocks into page await expect( - page + canvas .getByRole( 'document', { name: `Block: ${ blocks[ i ].name }`, exact: true, diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-patterns.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-patterns.spec.js index faa1ae369f112..d2b9699c52553 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-patterns.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/create-woocommerce-patterns.spec.js @@ -1,10 +1,11 @@ -const { test, expect } = require( '@playwright/test' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); - -const wooPatternsPageTitle = `Insert Woo Patterns ${ Date.now() }`; -const wooPatternsPageSlug = wooPatternsPageTitle - .replace( / /gi, '-' ) - .toLowerCase(); +const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); +const { + goToPageEditor, + fillPageTitle, + insertBlock, + getCanvas, + publishPage, +} = require( '../../utils/editor' ); // some WooCommerce Patterns to use const wooPatterns = [ @@ -26,75 +27,54 @@ const wooPatterns = [ }, ]; -test.describe( 'Insert WooCommerce Patterns Into Page', () => { - test.use( { storageState: process.env.ADMINSTATE } ); - - test( 'can insert WooCommerce patterns into page', async ( { page } ) => { - // go to create a new page - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - - await disableWelcomeModal( { page } ); +baseTest.describe( 'Add WooCommerce Patterns Into Page', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + testPageTitlePrefix: 'Woocommerce Patterns', + } ); - // fill page title - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( wooPatternsPageTitle ); + test( 'can insert WooCommerce patterns into page', async ( { + page, + testPage, + } ) => { + await goToPageEditor( { page } ); + await fillPageTitle( page, testPage.title ); - // add Woo Patterns and verify them as added into page for ( let i = 0; i < wooPatterns.length; i++ ) { - // click title field for block inserter to show up - await page.getByRole( 'textbox', { name: 'Add title' } ).click(); + await test.step( `Insert ${ wooPatterns[ i ].name } pattern`, async () => { + await insertBlock( page, wooPatterns[ i ].name ); - // add pattern into page - await page.getByLabel( 'Add block' ).click(); - await page - .getByPlaceholder( 'Search', { exact: true } ) - .fill( wooPatterns[ i ].name ); - await page - .getByRole( 'option', { - name: wooPatterns[ i ].name, - exact: true, - } ) - .click(); - await expect( - page.getByLabel( 'Dismiss this notice' ).filter( { - hasText: `Block pattern "${ wooPatterns[ i ].name }" inserted.`, - } ) - ).toBeVisible(); + await expect( + page.getByLabel( 'Dismiss this notice' ).filter( { + hasText: `Block pattern "${ wooPatterns[ i ].name }" inserted.`, + } ) + ).toBeVisible(); - // verify added patterns into page - await expect( - page - .getByRole( 'textbox' ) - .filter( { hasText: `${ wooPatterns[ i ].button }` } ) - ).toBeVisible(); + const canvas = await getCanvas( page ); + await expect( + canvas + .getByRole( 'textbox' ) + .filter( { hasText: `${ wooPatterns[ i ].button }` } ) + ).toBeVisible(); + } ); } - // save and publish the page - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ wooPatternsPageTitle } is now live.` ) - ).toBeVisible(); + await publishPage( page, testPage.title ); // check again added patterns after publishing + const canvas = await getCanvas( page ); for ( let i = 1; i < wooPatterns.length; i++ ) { await expect( - page + canvas .getByRole( 'textbox' ) .filter( { hasText: `${ wooPatterns[ i ].button }` } ) ).toBeVisible(); } // go to the frontend page to verify patterns - await page.goto( wooPatternsPageSlug ); + await page.goto( testPage.slug ); await expect( - page.getByRole( 'heading', { name: wooPatternsPageTitle } ) + page.getByRole( 'heading', { name: testPage.title } ) ).toBeVisible(); // check some elements from added patterns diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/customer-list.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/customer-list.spec.js index 48f660f8f9d1b..b28427c093297 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/customer-list.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/customer-list.spec.js @@ -251,7 +251,7 @@ baseTest.describe( 'Merchant > Customer List', () => { } ); await test.step( 'Add a filter for email', async () => { - await page.getByRole( 'button', { name: 'Add a Filter' } ).click(); + await page.getByRole( 'button', { name: 'Add a filter' } ).click(); await page .locator( 'li' ) .filter( { hasText: 'Email' } ) @@ -268,7 +268,7 @@ baseTest.describe( 'Merchant > Customer List', () => { } ); await test.step( 'Add a filter for country', async () => { - await page.getByRole( 'button', { name: 'Add a Filter' } ).click(); + await page.getByRole( 'button', { name: 'Add a filter' } ).click(); await page .locator( 'li' ) .filter( { hasText: 'Country / Region' } ) diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js index 596dd717f577b..96c20bd25d320 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/page-loads.spec.js @@ -27,19 +27,19 @@ const wcPages = [ }, { name: 'Reports', - heading: 'Orders', + heading: 'Reports', element: '.nav-tab-wrapper > .nav-tab-active', text: 'Orders', }, { name: 'Settings', - heading: 'General', + heading: 'Settings', element: '#store_address-description', text: 'This is where your business is located. Tax rates and shipping rates will use this address.', }, { name: 'Status', - heading: 'System status', + heading: 'Status', element: '.nav-tab-active', text: 'System status', }, @@ -127,9 +127,9 @@ for ( const currentPage of wcPages ) { const httpStatus = response.status; const { status, message } = response.data; - expect( httpStatus ).toEqual( 200 ); - expect( status ).toEqual( 'success' ); - expect( message ).toEqual( + test.expect( httpStatus ).toEqual( 200 ); + test.expect( status ).toEqual( 'success' ); + test.expect( message ).toEqual( 'Onboarding profile data has been updated.' ); const api = new wcApi( { @@ -145,8 +145,8 @@ for ( const currentPage of wcPages ) { type: 'simple', regular_price: productPrice, } ) - .then( ( response ) => { - productId = response.data.id; + .then( ( _response ) => { + productId = _response.data.id; } ); // create an order await api @@ -158,13 +158,13 @@ for ( const currentPage of wcPages ) { }, ], } ) - .then( ( response ) => { - orderId = response.data.id; + .then( ( _response ) => { + orderId = _response.data.id; } ); // create customer await api .post( 'customers', customer ) - .then( ( response ) => ( customer.id = response.data.id ) ); + .then( ( _response ) => ( customer.id = _response.data.id ) ); } ); test.afterAll( async ( { baseURL } ) => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-edit.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-edit.spec.js index 8e0940ff33873..23b90612ced2e 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-edit.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-edit.spec.js @@ -1,184 +1,177 @@ const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); -baseTest.describe( 'Products > Edit Product', () => { - const test = baseTest.extend( { - storageState: process.env.ADMINSTATE, - products: async ( { api }, use ) => { - const products = []; - - for ( let i = 0; i < 2; i++ ) { - await api - .post( 'products', { - id: 0, - name: `Product ${ i }_${ Date.now() }`, - type: 'simple', - regular_price: `${ 12.99 + i }`, - manage_stock: true, - stock_quantity: 10 + i, - stock_status: 'instock', - } ) - .then( ( response ) => { - products.push( response.data ); - } ); - } - - await use( products ); - - // Cleanup - for ( const product of products ) { - await api.delete( `products/${ product.id }`, { force: true } ); - } - }, +const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + products: async ( { api }, use ) => { + const products = []; + + for ( let i = 0; i < 2; i++ ) { + await api + .post( 'products', { + id: 0, + name: `Product ${ i }_${ Date.now() }`, + type: 'simple', + regular_price: `${ 12.99 + i }`, + manage_stock: true, + stock_quantity: 10 + i, + stock_status: 'instock', + } ) + .then( ( response ) => { + products.push( response.data ); + } ); + } + + await use( products ); + + // Cleanup + for ( const product of products ) { + await api.delete( `products/${ product.id }`, { force: true } ); + } + }, +} ); + +test( 'can edit a product and save the changes', async ( { + page, + products, +} ) => { + await page.goto( + `wp-admin/post.php?post=${ products[ 0 ].id }&action=edit` + ); + + const newProduct = { + name: `Product ${ Date.now() }`, + description: `This product is pretty awesome ${ Date.now() }`, + regularPrice: '100.05', + salePrice: '99.05', + }; + + await test.step( 'edit the product name', async () => { + await page.getByLabel( 'Product name' ).fill( newProduct.name ); + } ); + + await test.step( 'edit the product description', async () => { + await page.locator( '#content-html' ).click(); // text mode to work around iframe + await page + .locator( '.wp-editor-area' ) + .first() + .fill( newProduct.description ); + } ); + + await test.step( 'edit the product price', async () => { + await page + .getByLabel( 'Regular price ($)' ) + .fill( newProduct.regularPrice ); + await page.getByLabel( 'Sale price ($)' ).fill( newProduct.salePrice ); + } ); + + await test.step( 'publish the updated product', async () => { + await page.getByRole( 'button', { name: 'Update' } ).click(); } ); - test( 'can edit a product and save the changes', async ( { - page, - products, - } ) => { - await page.goto( - `wp-admin/post.php?post=${ products[ 0 ].id }&action=edit` + await test.step( 'verify the changes', async () => { + await expect( page.getByLabel( 'Product name' ) ).toHaveValue( + newProduct.name + ); + await expect( page.locator( '.wp-editor-area' ).first() ).toContainText( + newProduct.description + ); + await expect( page.getByLabel( 'Regular price ($)' ) ).toHaveValue( + newProduct.regularPrice + ); + await expect( page.getByLabel( 'Sale price ($)' ) ).toHaveValue( + newProduct.salePrice ); + } ); +} ); - const newProduct = { - name: `Product ${ Date.now() }`, - description: `This product is pretty awesome ${ Date.now() }`, - regularPrice: '100.05', - salePrice: '99.05', - }; - - await test.step( 'edit the product name', async () => { - await page.getByLabel( 'Product name' ).fill( newProduct.name ); - } ); - - await test.step( 'edit the product description', async () => { - await page.locator( '#content-html' ).click(); // text mode to work around iframe - await page - .locator( '.wp-editor-area' ) - .first() - .fill( newProduct.description ); - } ); - - await test.step( 'edit the product price', async () => { - await page - .getByLabel( 'Regular price ($)' ) - .fill( newProduct.regularPrice ); - await page - .getByLabel( 'Sale price ($)' ) - .fill( newProduct.salePrice ); - } ); - - await test.step( 'publish the updated product', async () => { - await page.getByRole( 'button', { name: 'Update' } ).click(); - } ); - - await test.step( 'verify the changes', async () => { - await expect( page.getByLabel( 'Product name' ) ).toHaveValue( - newProduct.name - ); - await expect( - page.locator( '.wp-editor-area' ).first() - ).toContainText( newProduct.description ); - await expect( page.getByLabel( 'Regular price ($)' ) ).toHaveValue( - newProduct.regularPrice - ); - await expect( page.getByLabel( 'Sale price ($)' ) ).toHaveValue( - newProduct.salePrice +test( 'can bulk edit products', async ( { page, products } ) => { + await page.goto( `wp-admin/edit.php?post_type=product` ); + + const regularPriceIncrease = 10; + const salePriceDecrease = 10; + const stockQtyIncrease = 10; + + await test.step( 'select and bulk edit the products', async () => { + for ( const product of products ) { + await page.getByLabel( `Select ${ product.name }` ).click(); + } + + await page + .locator( '#bulk-action-selector-top' ) + .selectOption( 'Edit' ); + await page.locator( '#doaction' ).click(); + + await expect( + await page.locator( '#bulk-titles-list li' ).count() + ).toEqual( products.length ); + } ); + + await test.step( 'update the regular price', async () => { + await page + .locator( 'select[name="change_regular_price"]' ) + .selectOption( 'Increase existing price by (fixed amount or %):' ); + await page + .getByPlaceholder( 'Enter price ($)' ) + .fill( `${ regularPriceIncrease }%` ); + } ); + + await test.step( 'update the sale price', async () => { + await page + .locator( 'select[name="change_sale_price"]' ) + .selectOption( + 'Set to regular price decreased by (fixed amount or %):' ); - } ); + await page + .getByPlaceholder( 'Enter sale price ($)' ) + .fill( `${ salePriceDecrease }%` ); + } ); + + await test.step( 'update the stock quantity', async () => { + await page + .locator( 'select[name="change_stock"]' ) + .selectOption( 'Increase existing stock by:' ); + await page + .getByPlaceholder( 'Stock qty' ) + .fill( `${ stockQtyIncrease }` ); + } ); + + await test.step( 'save the updates', async () => { + await page.getByRole( 'button', { name: 'Update' } ).click(); } ); - test( 'can bulk edit products', async ( { page, products } ) => { - await page.goto( `wp-admin/edit.php?post_type=product` ); - - const regularPriceIncrease = 10; - const salePriceDecrease = 10; - const stockQtyIncrease = 10; - - await test.step( 'select and bulk edit the products', async () => { - for ( const product of products ) { - await page.getByLabel( `Select ${ product.name }` ).click(); - } - - await page - .locator( '#bulk-action-selector-top' ) - .selectOption( 'Edit' ); - await page.locator( '#doaction' ).click(); - - await expect( - await page.locator( '#bulk-titles-list li' ).count() - ).toEqual( products.length ); - } ); - - await test.step( 'update the regular price', async () => { - await page - .locator( 'select[name="change_regular_price"]' ) - .selectOption( - 'Increase existing price by (fixed amount or %):' - ); - await page - .getByPlaceholder( 'Enter price ($)' ) - .fill( `${ regularPriceIncrease }%` ); - } ); - - await test.step( 'update the sale price', async () => { - await page - .locator( 'select[name="change_sale_price"]' ) - .selectOption( - 'Set to regular price decreased by (fixed amount or %):' - ); - await page - .getByPlaceholder( 'Enter sale price ($)' ) - .fill( `${ salePriceDecrease }%` ); - } ); - - await test.step( 'update the stock quantity', async () => { - await page - .locator( 'select[name="change_stock"]' ) - .selectOption( 'Increase existing stock by:' ); - await page - .getByPlaceholder( 'Stock qty' ) - .fill( `${ stockQtyIncrease }` ); - } ); - - await test.step( 'save the updates', async () => { - await page.getByRole( 'button', { name: 'Update' } ).click(); - } ); - - await test.step( 'verify the changes', async () => { - for ( const product of products ) { - await page.goto( `product/${ product.slug }` ); - - const expectedRegularPrice = ( - product.regular_price * - ( 1 + regularPriceIncrease / 100 ) - ).toFixed( 2 ); - const expectedSalePrice = ( - expectedRegularPrice * - ( 1 - salePriceDecrease / 100 ) - ).toFixed( 2 ); - const expectedStockQty = - product.stock_quantity + stockQtyIncrease; - - await expect - .soft( - await page - .locator( 'del' ) - .getByText( `$${ expectedRegularPrice }` ) - .count() - ) - .toBeGreaterThan( 0 ); - await expect - .soft( - await page - .locator( 'ins' ) - .getByText( `$${ expectedSalePrice }` ) - .count() - ) - .toBeGreaterThan( 0 ); - await expect - .soft( page.getByText( `${ expectedStockQty } in stock` ) ) - .toBeVisible(); - } - } ); + await test.step( 'verify the changes', async () => { + for ( const product of products ) { + await page.goto( `product/${ product.slug }` ); + + const expectedRegularPrice = ( + product.regular_price * + ( 1 + regularPriceIncrease / 100 ) + ).toFixed( 2 ); + const expectedSalePrice = ( + expectedRegularPrice * + ( 1 - salePriceDecrease / 100 ) + ).toFixed( 2 ); + const expectedStockQty = product.stock_quantity + stockQtyIncrease; + + await expect + .soft( + await page + .locator( 'del' ) + .getByText( `$${ expectedRegularPrice }` ) + .count() + ) + .toBeGreaterThan( 0 ); + await expect + .soft( + await page + .locator( 'ins' ) + .getByText( `$${ expectedSalePrice }` ) + .count() + ) + .toBeGreaterThan( 0 ); + await expect + .soft( page.getByText( `${ expectedStockQty } in stock` ) ) + .toBeVisible(); + } } ); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/create-variable-product-block-editor.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/create-variable-product-block-editor.spec.js index 4c267a658c602..3fe24967f28b3 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/create-variable-product-block-editor.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/create-variable-product-block-editor.spec.js @@ -196,10 +196,9 @@ test.describe( 'Variations tab', () => { await clickOnTab( 'Variations', page ); await page - .locator( - '.woocommerce-variations-table-error-or-empty-state__actions' - ) - .getByRole( 'button', { name: 'Generate from options' } ) + .getByRole( 'button', { + name: 'Generate from options', + } ) .click(); await clickOnTab( 'Variations', page ); @@ -215,21 +214,17 @@ test.describe( 'Variations tab', () => { .getByRole( 'button', { name: 'Pricing' } ) .click(); - const regularPrice = page.locator( 'input[name="regular_price"]' ); - await regularPrice.waitFor( { state: 'visible' } ); - await regularPrice.first().click(); - await regularPrice.first().fill( '100' ); + await page + .getByLabel( 'Regular price', { exact: true } ) + .fill( '100' ); await page .locator( '.woocommerce-product-tabs' ) .getByRole( 'button', { name: 'Inventory' } ) .click(); - const sku = page.locator( 'input[name="woocommerce-product-sku"]' ); - await sku.waitFor( { state: 'visible' } ); - await sku.first().click(); - await sku - .first() + await page + .locator( '#inspector-input-control-2' ) .fill( `product-sku-${ new Date().getTime().toString() }` ); await page @@ -250,16 +245,13 @@ test.describe( 'Variations tab', () => { } ) .click(); - const editedItem = page - .locator( '.woocommerce-product-variations__table-body > div' ) - .first(); - - const isEditedItemVisible = await editedItem - .locator( 'text="$100.00"' ) - .waitFor( { state: 'visible', timeout: 3000 } ) - .then( () => true ) - .catch( () => false ); - expect( isEditedItemVisible ).toBeTruthy(); + await expect( + page + .locator( + '.woocommerce-product-variations__table-body > div' + ) + .first() + ).toBeVisible(); } ); test( 'can delete a variation', async ( { page } ) => { @@ -342,14 +334,15 @@ test.describe( 'Variations tab', () => { `/wp-admin/admin.php?page=wc-admin&path=/product/${ productId_deleteVariations }&tab=variations` ); - await page.waitForSelector( - '.woocommerce-product-variations__table-body > div' - ); + await expect( + page.getByText( + 'variations do not have prices. Variations that do not have prices will not be visible to customers.Set prices' + ) + ).toBeVisible(); await page - .locator( '.woocommerce-product-variations__table-body > div' ) + .getByRole( 'link', { name: 'Edit', exact: true } ) .first() - .getByText( 'Edit' ) .click(); const notices = page.getByText( diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/linked-product-tab-product-block-editor.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/linked-product-tab-product-block-editor.spec.js index d72492f0a2d6b..d5d4a21ca2898 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/linked-product-tab-product-block-editor.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/linked-product-tab-product-block-editor.spec.js @@ -63,11 +63,11 @@ test.describe( 'General tab', () => { await clickOnTab( 'Linked products', page ); - await page - .getByRole( 'heading', { + await expect( + page.getByRole( 'heading', { name: 'Cross-sells', } ) - .isVisible(); + ).toBeVisible(); await page .locator( @@ -79,9 +79,7 @@ test.describe( 'General tab', () => { await page.getByText( linkedProductsData[ 0 ].name ).click(); - await page.getByText( 'Choose products for me' ).first().click(); - - await page.waitForResponse( + const chooseProductsResponsePromise = page.waitForResponse( ( response ) => response .url() @@ -90,20 +88,18 @@ test.describe( 'General tab', () => { ) && response.status() === 200 ); - await page.waitForFunction( - ( [ selector, expectedCount ] ) => - document.querySelectorAll( selector ).length === - expectedCount, - [ '.woocommerce-product-list div[role="row"]', 4 ] - ); + await page.getByText( 'Choose products for me' ).first().click(); + await chooseProductsResponsePromise; + + await expect( + page.getByRole( 'row', { name: 'Product name' } ) + ).toHaveCount( 4 ); const upsellsRows = page.locator( 'div.woocommerce-product-list div[role="table"] div[role="rowgroup"] div[role="row"]' ); - const upsellsRowsCount = await upsellsRows.count(); - - await expect( upsellsRowsCount ).toBe( 4 ); + await expect( upsellsRows ).toHaveCount( 4 ); await page .locator( @@ -157,9 +153,7 @@ test.describe( 'General tab', () => { 'section.upsells.products ul > li' ); - const productsCount = await productsList.count(); - - await expect( productsCount ).toBe( 4 ); + await expect( productsList ).toHaveCount( 4 ); } ); } ); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/product-edit-block-editor.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/product-edit-block-editor.spec.js index 3fbdccc87dca2..41012e7f6a60e 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/product-edit-block-editor.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/products/block-editor/product-edit-block-editor.spec.js @@ -80,3 +80,84 @@ test( 'can update the general information of a product', async ( { .toHaveText( updatedProduct.description ); } ); } ); + +test.describe( 'Publish dropdown options', () => { + test( 'can schedule a product publication', async ( { page, product } ) => { + await page.goto( `wp-admin/post.php?post=${ product.id }&action=edit` ); + + await page + .locator( '.woocommerce-product-header__actions' ) + .first() + .locator( 'button[aria-label="More options"]' ) + .click(); + + await page.getByText( 'Schedule publish' ).click(); + + await expect( + page.getByRole( 'heading', { name: 'Schedule product' } ) + ).toBeVisible(); + + await page + .locator( '.woocommerce-schedule-publish-modal' ) + .locator( 'button[aria-label="View next month"]' ) + .click(); + + await page + .locator( '.woocommerce-schedule-publish-modal' ) + .getByText( '14' ) + .click(); + + await page.getByRole( 'button', { name: 'Schedule' } ).click(); + + await expect( + page.getByLabel( 'Dismiss this notice' ).first() + ).toContainText( 'Product scheduled for' ); + } ); + test( 'can duplicate a product', async ( { page, product } ) => { + await page.goto( `wp-admin/post.php?post=${ product.id }&action=edit` ); + await page + .locator( '.woocommerce-product-header__actions' ) + .first() + .locator( 'button[aria-label="More options"]' ) + .click(); + + await page.getByText( 'Copy to a new draft' ).click(); + + await expect( + page.getByLabel( 'Dismiss this notice' ).first() + ).toContainText( 'Product successfully duplicated' ); + + await expect( + page.getByRole( 'heading', { name: `${ product.name } (Copy)` } ) + ).toBeVisible(); + + await expect( + page + .locator( '.woocommerce-product-header__visibility-tags' ) + .getByText( 'Draft' ) + .first() + ).toBeVisible(); + + await page + .locator( '.woocommerce-product-header__actions' ) + .first() + .locator( 'button[aria-label="More options"]' ) + .click(); + + await page.getByText( 'Move to trash' ).click(); + } ); + test( 'can delete a product', async ( { page, product } ) => { + await page.goto( `wp-admin/post.php?post=${ product.id }&action=edit` ); + await page + .locator( '.woocommerce-product-header__actions' ) + .first() + .locator( 'button[aria-label="More options"]' ) + .click(); + + await page.getByText( 'Move to trash' ).click(); + + await expect( + page.getByRole( 'heading', { name: 'Products' } ).first() + ).toBeVisible(); + } ); +} ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/users-manage.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/users-manage.spec.js index 0ea57a83aeb1f..a8e0009981546 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/users-manage.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/users-manage.spec.js @@ -55,7 +55,7 @@ const test = baseTest.extend( { async function userDeletionTest( page, username ) { await page.goto( `wp-admin/users.php?s=${ username }` ); - await test.step( 'hover the the username and delete', async () => { + await test.step( 'hover the username and delete', async () => { await page.getByRole( 'link', { name: username, exact: true } ).hover(); await page.getByRole( 'link', { name: 'Delete' } ).click(); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/account-email-receiving.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/account-email-receiving.spec.js index b8c2879bde05d..8ef89a37bcbe4 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/account-email-receiving.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/account-email-receiving.spec.js @@ -44,6 +44,8 @@ test.describe( 'Shopper Account Email Receiving', () => { // create a new customer await page.goto( 'wp-admin/user-new.php' ); + await page.waitForLoadState( 'networkidle' ); + await page.getByLabel( ' Username (required) ' ).fill( username ); await page.getByLabel( ' Email (required) ' ).fill( email ); await page.getByLabel( ' First Name ' ).fill( 'New' ); @@ -86,6 +88,8 @@ test.describe( 'Shopper Account Email Receiving', () => { // create a new customer await page.goto( 'wp-admin/user-new.php' ); + await page.waitForLoadState( 'networkidle' ); + await page.getByLabel( ' Username (required) ' ).fill( username ); await page.getByLabel( ' Email (required) ' ).fill( email ); await page.getByLabel( ' First Name ' ).fill( 'New' ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-calculate-shipping.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-calculate-shipping.spec.js index 2a54d04eb23a9..adac3c135853b 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-calculate-shipping.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-calculate-shipping.spec.js @@ -1,37 +1,40 @@ -const { test, expect } = require( '@playwright/test' ); -const { admin } = require( '../../test-data/data' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); -const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; +const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); +const { + goToPageEditor, + fillPageTitle, + insertBlockByShortcut, + publishPage, +} = require( '../../utils/editor' ); +const { addAProductToCart } = require( '../../utils/cart' ); const firstProductName = 'First Product'; const firstProductPrice = '10.00'; const secondProductName = 'Second Product'; const secondProductPrice = '20.00'; const firstProductWithFlatRate = +firstProductPrice + 5; -const twoProductsTotal = +firstProductPrice + +secondProductPrice; -const twoProductsWithFlatRate = twoProductsTotal + 5; - -const cartBlockPageTitle = 'Cart Block'; -const cartBlockPageSlug = cartBlockPageTitle - .replace( / /gi, '-' ) - .toLowerCase(); const shippingZoneNameES = 'Netherlands Free Shipping'; const shippingCountryNL = 'NL'; const shippingZoneNamePT = 'Portugal Flat Local'; const shippingCountryPT = 'PT'; -test.describe( 'Cart Block Calculate Shipping', () => { - let product1Id, product2Id, shippingZoneNLId, shippingZonePTId; +baseTest.describe( 'Cart Block Calculate Shipping', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + testPageTitlePrefix: 'Cart Block', + cartBlockPage: async ( { page, testPage }, use ) => { + await goToPageEditor( { page } ); + await fillPageTitle( page, testPage.title ); + await insertBlockByShortcut( page, '/cart' ); + await publishPage( page, testPage.title ); + + await use( testPage ); + }, + } ); - test.beforeAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + let product1Id, product2Id, shippingZoneNLId, shippingZonePTId; + test.beforeAll( async ( { api } ) => { // make sure the currency is USD await api.put( 'settings/general/woocommerce_currency', { value: 'USD', @@ -112,13 +115,7 @@ test.describe( 'Cart Block Calculate Shipping', () => { } ); } ); - test.afterAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.afterAll( async ( { api } ) => { await api.post( 'products/batch', { delete: [ product1Id, product2Id ], } ); @@ -130,57 +127,18 @@ test.describe( 'Cart Block Calculate Shipping', () => { } ); } ); - test.beforeEach( async ( { page, context } ) => { - // Shopping cart is very sensitive to cookies, so be explicit - await context.clearCookies(); - - // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ product1Id }` ); - await page.waitForLoadState( 'networkidle' ); - } ); - - test( 'create Cart Block page', async ( { page } ) => { - // create a new page with cart block - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - await page.locator( 'input[name="log"]' ).fill( admin.username ); - await page.locator( 'input[name="pwd"]' ).fill( admin.password ); - await page.locator( 'text=Log In' ).click(); - - await disableWelcomeModal( { page } ); - - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( cartBlockPageTitle ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); - await page - .getByRole( 'document', { - name: 'Empty block; start writing or type forward slash to choose a block', - } ) - .fill( '/cart' ); - await page.keyboard.press( 'Enter' ); - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ cartBlockPageTitle } is now live.` ) - ).toBeVisible(); - } ); - test( 'allows customer to calculate Free Shipping in cart block if in Netherlands', async ( { page, + context, + cartBlockPage, } ) => { - await page.goto( cartBlockPageSlug ); + await context.clearCookies(); + + await addAProductToCart( page, product1Id ); + await page.goto( cartBlockPage.slug ); // Set shipping country to Netherlands - await page - .locator( - '.wc-block-components-totals-shipping__change-address__link' - ) - .click(); + await page.getByLabel( 'Add an address for shipping' ).click(); await page.getByRole( 'combobox' ).first().fill( 'Netherlands' ); await page.getByLabel( 'Postal code' ).fill( '1011AA' ); await page.getByLabel( 'City' ).fill( 'Amsterdam' ); @@ -190,29 +148,24 @@ test.describe( 'Cart Block Calculate Shipping', () => { await expect( page.getByRole( 'group' ).getByText( 'Free shipping' ) ).toBeVisible(); - await expect( - page.locator( - '.wc-block-components-radio-control__description > .wc-block-components-formatted-money-amount' - ) - ).toContainText( '$0.00' ); - await expect( - page.locator( - '.wc-block-components-totals-footer-item > .wc-block-components-totals-item__value' - ) - ).toContainText( firstProductPrice ); + await expect( page.getByText( 'Free', { exact: true } ) ).toBeVisible(); + await expect( page.getByText( '$' ).nth( 4 ) ).toContainText( + firstProductPrice + ); } ); test( 'allows customer to calculate Flat rate and Local pickup in cart block if in Portugal', async ( { page, + context, + cartBlockPage, } ) => { - await page.goto( cartBlockPageSlug ); + await context.clearCookies(); + + await addAProductToCart( page, product1Id ); + await page.goto( cartBlockPage.slug ); // Set shipping country to Portugal - await page - .locator( - '.wc-block-components-totals-shipping__change-address__link' - ) - .click(); + await page.getByLabel( 'Add an address for shipping' ).click(); await page.getByRole( 'combobox' ).first().fill( 'Portugal' ); await page.getByLabel( 'Postal code' ).fill( '1000-001' ); await page.getByLabel( 'City' ).fill( 'Lisbon' ); @@ -222,91 +175,64 @@ test.describe( 'Cart Block Calculate Shipping', () => { await expect( page.getByRole( 'group' ).getByText( 'Flat rate' ) ).toBeVisible(); + await expect( page.getByText( 'Shipping$5.00Flat' ) ).toBeVisible(); await expect( - page.locator( - '.wc-block-components-totals-shipping > .wc-block-components-totals-item' - ) - ).toContainText( '$5.00' ); - await expect( - page.locator( - '.wc-block-components-totals-footer-item > .wc-block-components-totals-item__value' - ) - ).toContainText( firstProductWithFlatRate.toString() ); + page.getByText( `$${ firstProductWithFlatRate }` ) + ).toBeVisible(); // Set shipping to local pickup instead of flat rate await page.getByRole( 'group' ).getByText( 'Local pickup' ).click(); // Verify updated shipping costs - await expect( - page.locator( - '.wc-block-components-totals-shipping > .wc-block-components-totals-item' - ) - ).toContainText( '$0.00' ); - let totalPrice = await page - .locator( - '.wc-block-components-totals-footer-item > .wc-block-components-totals-item__value' - ) - .last() - .textContent(); - totalPrice = Number( totalPrice.replace( /\$([\d.]+).*/, '$1' ) ); - await expect( totalPrice ).toBeGreaterThanOrEqual( - Number( firstProductPrice ) - ); - await expect( totalPrice ).toBeLessThanOrEqual( - Number( firstProductPrice * 1.25 ) + await expect( page.getByText( 'Shipping$0.00Local' ) ).toBeVisible(); + await expect( page.getByText( '$' ).nth( 5 ) ).toContainText( + firstProductPrice ); } ); test( 'should show correct total cart block price after updating quantity', async ( { page, + context, + cartBlockPage, } ) => { - await page.goto( cartBlockPageSlug ); + await context.clearCookies(); + + await addAProductToCart( page, product1Id ); + await page.goto( cartBlockPage.slug ); // Set shipping country to Portugal - await page - .locator( - '.wc-block-components-totals-shipping__change-address__link' - ) - .click(); + await page.getByLabel( 'Add an address for shipping' ).click(); await page.getByRole( 'combobox' ).first().fill( 'Portugal' ); await page.getByLabel( 'Postal code' ).fill( '1000-001' ); await page.getByLabel( 'City' ).fill( 'Lisbon' ); await page.getByRole( 'button', { name: 'Update' } ).click(); // Increase product quantity and verify the updated price - await page - .getByRole( 'button' ) - .filter( { hasText: '+', exact: true } ) - .click(); - let totalPrice = await page - .locator( - '.wc-block-components-totals-footer-item > .wc-block-components-totals-item__value' + await page.getByLabel( 'Increase quantity of First' ).click(); + await expect( + page.getByText( + `$${ + parseInt( firstProductPrice, 10 ) + + parseInt( firstProductPrice, 10 ) + + 5 + }`.toString() ) - .last() - .textContent(); - totalPrice = Number( totalPrice.replace( /\$([\d.]+).*/, '$1' ) ); - await expect( totalPrice ).toBeGreaterThanOrEqual( - Number( firstProductPrice ) - ); - await expect( totalPrice ).toBeLessThanOrEqual( - Number( firstProductPrice * 1.25 ) - ); + ).toBeVisible(); } ); test( 'should show correct total cart block price with 2 different products and flat rate/local pickup', async ( { page, + context, + cartBlockPage, } ) => { - await page.goto( `/shop/?add-to-cart=${ product2Id }` ); - await page.waitForLoadState( 'networkidle' ); + await context.clearCookies(); - await page.goto( cartBlockPageSlug ); + await addAProductToCart( page, product1Id ); + await addAProductToCart( page, product2Id ); + await page.goto( cartBlockPage.slug ); // Set shipping country to Portugal - await page - .locator( - '.wc-block-components-totals-shipping__change-address__link' - ) - .click(); + await page.getByLabel( 'Add an address for shipping' ).click(); await page.getByRole( 'combobox' ).first().fill( 'Portugal' ); await page.getByLabel( 'Postal code' ).fill( '1000-001' ); await page.getByLabel( 'City' ).fill( 'Lisbon' ); @@ -316,46 +242,27 @@ test.describe( 'Cart Block Calculate Shipping', () => { await expect( page.getByRole( 'group' ).getByText( 'Flat rate' ) ).toBeVisible(); + await expect( page.getByText( 'Shipping$5.00Flat' ) ).toBeVisible(); await expect( - page.locator( - '.wc-block-components-totals-shipping > .wc-block-components-totals-item' + page.getByText( + `$${ + parseInt( firstProductPrice, 10 ) + + parseInt( secondProductPrice, 10 ) + + 5 + }`.toString() ) - ).toContainText( '$5.00' ); - let totalPrice = await page - .locator( - '.wc-block-components-totals-footer-item > .wc-block-components-totals-item__value' - ) - .last() - .textContent(); - totalPrice = Number( totalPrice.replace( /\$([\d.]+).*/, '$1' ) ); - await expect( totalPrice ).toBeGreaterThanOrEqual( - Number( twoProductsWithFlatRate ) - ); - await expect( totalPrice ).toBeLessThanOrEqual( - Number( twoProductsWithFlatRate * 1.25 ) - ); + ).toBeVisible(); // Set shipping to local pickup instead of flat rate await page.getByRole( 'group' ).getByText( 'Local pickup' ).click(); // Verify updated shipping costs + await expect( page.getByText( 'Shipping$0.00Local' ) ).toBeVisible(); await expect( - page.locator( - '.wc-block-components-totals-shipping > .wc-block-components-totals-item' - ) - ).toContainText( '$0.00' ); - totalPrice = await page - .locator( - '.wc-block-components-totals-footer-item > .wc-block-components-totals-item__value' - ) - .last() - .textContent(); - totalPrice = Number( totalPrice.replace( /\$([\d.]+).*/, '$1' ) ); - await expect( totalPrice ).toBeGreaterThanOrEqual( - Number( twoProductsTotal ) - ); - await expect( totalPrice ).toBeLessThanOrEqual( - Number( twoProductsTotal * 1.25 ) - ); + page + .locator( 'div' ) + .filter( { hasText: /^\$30\.00$/ } ) + .locator( 'span' ) + ).toBeVisible(); } ); } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-coupons.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-coupons.spec.js index c64a0ba79e2a8..a1ec1228bb31b 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-coupons.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block-coupons.spec.js @@ -1,7 +1,11 @@ -const { test, expect } = require( '@playwright/test' ); -const { admin } = require( '../../test-data/data' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); -const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; +const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); +const { + goToPageEditor, + fillPageTitle, + insertBlockByShortcut, + publishPage, +} = require( '../../utils/editor' ); +const { addAProductToCart } = require( '../../utils/cart' ); const simpleProductName = 'Cart Coupons Product'; const singleProductFullPrice = '110.00'; @@ -28,23 +32,33 @@ const customerBilling = { email: 'john.doe.merchant.test@example.com', }; -const cartBlockPageTitle = 'Cart Block'; -const cartBlockPageSlug = cartBlockPageTitle - .replace( / /gi, '-' ) - .toLowerCase(); - let productId, orderId, limitedCouponId; -test.describe( 'Cart Block Applying Coupons', () => { +baseTest.describe( 'Cart Block Applying Coupons', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + testPageTitlePrefix: 'Cart Block', + page: async ( { context, page, testPage }, use ) => { + await goToPageEditor( { page } ); + await fillPageTitle( page, testPage.title ); + await insertBlockByShortcut( page, '/cart' ); + await publishPage( page, testPage.title ); + + await context.clearCookies(); + + await addAProductToCart( page, productId ); + await page.goto( testPage.slug ); + await expect( + page.getByRole( 'heading', { name: testPage.title } ) + ).toBeVisible(); + + await use( page ); + }, + } ); + const couponBatchId = []; - test.beforeAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.beforeAll( async ( { api } ) => { // make sure the currency is USD await api.put( 'settings/general/woocommerce_currency', { value: 'USD', @@ -98,13 +112,7 @@ test.describe( 'Cart Block Applying Coupons', () => { } ); } ); - test.afterAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.afterAll( async ( { api } ) => { await api.post( 'products/batch', { delete: [ productId ], } ); @@ -116,53 +124,10 @@ test.describe( 'Cart Block Applying Coupons', () => { } ); } ); - test.beforeEach( async ( { context } ) => { - // Shopping cart is very sensitive to cookies, so be explicit - await context.clearCookies(); - } ); - - test( 'can create Cart Block page', async ( { page } ) => { - // create a new page with cart block - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - await page.locator( 'input[name="log"]' ).fill( admin.username ); - await page.locator( 'input[name="pwd"]' ).fill( admin.password ); - await page.locator( 'text=Log In' ).click(); - - await disableWelcomeModal( { page } ); - - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( cartBlockPageTitle ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); - await page - .getByRole( 'document', { - name: 'Empty block; start writing or type forward slash to choose a block', - } ) - .fill( '/cart' ); - await page.keyboard.press( 'Enter' ); - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ cartBlockPageTitle } is now live.` ) - ).toBeVisible(); - } ); - test( 'allows cart block to apply coupon of any type', async ( { page, } ) => { const totals = [ '$50.00', '$27.50', '$45.00' ]; - // add product to cart block - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); - await page.goto( cartBlockPageSlug ); - await expect( - page.getByRole( 'heading', { name: cartBlockPageTitle } ) - ).toBeVisible(); // apply all coupon types for ( let i = 0; i < coupons.length; i++ ) { @@ -200,13 +165,6 @@ test.describe( 'Cart Block Applying Coupons', () => { const totals = [ '$50.00', '$22.50', '$12.50' ]; const totalsReverse = [ '$17.50', '$45.00', '$55.00' ]; const discounts = [ '-$5.00', '-$32.50', '-$42.50' ]; - // add product to cart block - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); - await page.goto( cartBlockPageSlug ); - await expect( - page.getByRole( 'heading', { name: cartBlockPageTitle } ) - ).toBeVisible(); // add all coupons and verify prices for ( let i = 0; i < coupons.length; i++ ) { @@ -249,14 +207,6 @@ test.describe( 'Cart Block Applying Coupons', () => { test( 'prevents cart block applying same coupon twice', async ( { page, } ) => { - // add product to cart block - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); - await page.goto( cartBlockPageSlug ); - await expect( - page.getByRole( 'heading', { name: cartBlockPageTitle } ) - ).toBeVisible(); - // try to add two same coupons and verify the error message await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page @@ -287,14 +237,6 @@ test.describe( 'Cart Block Applying Coupons', () => { test( 'prevents cart block applying coupon with usage limit', async ( { page, } ) => { - // add product to cart block and go to cart - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); - await page.goto( cartBlockPageSlug ); - await expect( - page.getByRole( 'heading', { name: cartBlockPageTitle } ) - ).toBeVisible(); - // add coupon with usage limit await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block.spec.js index a994c4d4cf7a5..c08a4cbba6607 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-block.spec.js @@ -1,6 +1,11 @@ -const { test, expect } = require( '@playwright/test' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); -const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; +const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); +const { + goToPageEditor, + fillPageTitle, + insertBlockByShortcut, + publishPage, +} = require( '../../utils/editor' ); +const { addAProductToCart } = require( '../../utils/cart' ); const simpleProductName = 'Single Simple Product'; const simpleProductDesc = 'Lorem ipsum dolor sit amet.'; @@ -14,23 +19,15 @@ const singleProductWithCrossSellProducts = +firstCrossSellProductPrice + +secondCrossSellProductPrice; -const cartBlockPageTitle = `Cart Block ${ Date.now() }`; -const cartBlockPageSlug = cartBlockPageTitle - .replace( / /gi, '-' ) - .toLowerCase(); - let product1Id, product2Id, product3Id; -test.describe( 'Cart Block page', () => { - test.use( { storageState: process.env.ADMINSTATE } ); +baseTest.describe( 'Cart Block page', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + testPageTitlePrefix: 'Cart Block', + } ); - test.beforeAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.beforeAll( async ( { api } ) => { // make sure the currency is USD await api.put( 'settings/general/woocommerce_currency', { value: 'USD', @@ -70,13 +67,7 @@ test.describe( 'Cart Block page', () => { } ); } ); - test.afterAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.afterAll( async ( { api } ) => { await api.post( 'products/batch', { delete: [ product1Id, product2Id, product3Id ], } ); @@ -84,37 +75,17 @@ test.describe( 'Cart Block page', () => { test( 'can see empty cart, add and remove simple & cross sell product, increase to max quantity', async ( { page, + testPage, } ) => { - // create a new page with cart block - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - - await disableWelcomeModal( { page } ); - - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( cartBlockPageTitle ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); - await page - .getByRole( 'document', { - name: 'Empty block; start writing or type forward slash to choose a block', - } ) - .fill( '/cart' ); - await page.keyboard.press( 'Enter' ); - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ cartBlockPageTitle } is now live.` ) - ).toBeVisible(); + await goToPageEditor( { page } ); + await fillPageTitle( page, testPage.title ); + await insertBlockByShortcut( page, '/cart' ); + await publishPage( page, testPage.title ); // go to the page to test empty cart block - await page.goto( cartBlockPageSlug ); + await page.goto( testPage.slug ); await expect( - page.getByRole( 'heading', { name: cartBlockPageTitle } ) + page.getByRole( 'heading', { name: testPage.title } ) ).toBeVisible(); await expect( await page.getByText( 'Your cart is currently empty!' ).count() @@ -127,11 +98,10 @@ test.describe( 'Cart Block page', () => { page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible(); - // add product to cart block - await page.goto( `/shop/?add-to-cart=${ product1Id }` ); - await page.goto( cartBlockPageSlug ); + await addAProductToCart( page, product1Id ); + await page.goto( testPage.slug ); await expect( - page.getByRole( 'heading', { name: cartBlockPageTitle } ) + page.getByRole( 'heading', { name: testPage.title } ) ).toBeVisible(); await expect( page.getByRole( 'link', { name: simpleProductName, exact: true } ) @@ -177,9 +147,9 @@ test.describe( 'Cart Block page', () => { .getByText( `${ simpleProductName } Cross-Sell 2` ) ).toBeVisible(); - await page.goto( cartBlockPageSlug ); + await page.goto( testPage.slug ); await expect( - page.getByRole( 'heading', { name: cartBlockPageTitle } ) + page.getByRole( 'heading', { name: testPage.title } ) ).toBeVisible(); await expect( page.getByRole( 'heading', { name: 'You may be interested in…' } ) diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-calculate-shipping.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-calculate-shipping.spec.js index c0051517c1399..d214570d25236 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-calculate-shipping.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-calculate-shipping.spec.js @@ -1,3 +1,4 @@ +const { addAProductToCart } = require( '../../utils/cart' ); const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; @@ -97,10 +98,7 @@ test.describe( 'Cart Calculate Shipping', () => { test.beforeEach( async ( { page, context } ) => { // Shopping cart is very sensitive to cookies, so be explicit await context.clearCookies(); - - // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); } ); test.afterAll( async ( { baseURL } ) => { @@ -194,8 +192,7 @@ test.describe( 'Cart Calculate Shipping', () => { test( 'should show correct total cart price with 2 products and flat rate', async ( { page, } ) => { - await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, secondProductId ); await page.goto( '/cart/' ); await page.locator( 'a.shipping-calculator-button' ).click(); @@ -215,8 +212,7 @@ test.describe( 'Cart Calculate Shipping', () => { test( 'should show correct total cart price with 2 products without flat rate', async ( { page, } ) => { - await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, secondProductId ); // Set shipping country to Spain await page.goto( '/cart/' ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-block-calculate-tax.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-block-calculate-tax.spec.js index 40f5dd3b34264..bfcc372b4728b 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-block-calculate-tax.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-block-calculate-tax.spec.js @@ -1,18 +1,24 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; -const { admin } = require( '../../test-data/data' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); +const { + goToPageEditor, + fillPageTitle, + insertBlockByShortcut, + publishPage, +} = require( '../../utils/editor' ); +const { addAProductToCart } = require( '../../utils/cart' ); +const { random } = require( '../../utils/helpers' ); const productName = 'First Product Cart Block Taxing'; const productPrice = '100.00'; const messyProductPrice = '13.47'; const secondProductName = 'Second Product Cart Block Taxing'; -const cartBlockPageTitle = 'Cart Block'; +const cartBlockPageTitle = `Cart Block ${ random() }`; const cartBlockPageSlug = cartBlockPageTitle .replace( / /gi, '-' ) .toLowerCase(); -const checkoutBlockPageTitle = 'Checkout Block'; +const checkoutBlockPageTitle = `Checkout Block ${ random() }`; const checkoutBlockPageSlug = checkoutBlockPageTitle .replace( / /gi, '-' ) .toLowerCase(); @@ -31,6 +37,7 @@ let productId, shippingMethodId; test.describe( 'Shopper Cart & Checkout Block Tax Display', () => { + test.use( { storageState: process.env.ADMINSTATE } ); test.beforeAll( async ( { baseURL } ) => { const api = new wcApi( { url: baseURL, @@ -47,6 +54,9 @@ test.describe( 'Shopper Cart & Checkout Block Tax Display', () => { await api.put( 'settings/tax/woocommerce_tax_total_display', { value: 'itemized', } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'incl', + } ); await api .post( 'products', { name: productName, @@ -71,16 +81,6 @@ test.describe( 'Shopper Cart & Checkout Block Tax Display', () => { } ); } ); - test.beforeEach( async ( { page, context } ) => { - // shopping cart is very sensitive to cookies, so be explicit - await context.clearCookies(); - - // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); - } ); - test.afterAll( async ( { baseURL } ) => { const api = new wcApi( { url: baseURL, @@ -105,71 +105,30 @@ test.describe( 'Shopper Cart & Checkout Block Tax Display', () => { } ); } ); + // eslint-disable-next-line playwright/expect-expect test( 'can create Cart Block page', async ( { page } ) => { - // create a new page with cart block - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - await page.locator( 'input[name="log"]' ).fill( admin.username ); - await page.locator( 'input[name="pwd"]' ).fill( admin.password ); - await page.locator( 'text=Log In' ).click(); - - await disableWelcomeModal( { page } ); - - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( cartBlockPageTitle ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); - await page - .getByRole( 'document', { - name: 'Empty block; start writing or type forward slash to choose a block', - } ) - .fill( '/cart' ); - await page.keyboard.press( 'Enter' ); - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ cartBlockPageTitle } is now live.` ) - ).toBeVisible(); + await goToPageEditor( { page } ); + await fillPageTitle( page, cartBlockPageTitle ); + await insertBlockByShortcut( page, '/cart' ); + await publishPage( page, cartBlockPageTitle ); } ); + // eslint-disable-next-line playwright/expect-expect test( 'can create Checkout Block page', async ( { page } ) => { - // create a new page with checkout block - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - await page.locator( 'input[name="log"]' ).fill( admin.username ); - await page.locator( 'input[name="pwd"]' ).fill( admin.password ); - await page.locator( 'text=Log In' ).click(); - - await disableWelcomeModal( { page } ); - - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( checkoutBlockPageTitle ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); - await page - .getByRole( 'document', { - name: 'Empty block; start writing or type forward slash to choose a block', - } ) - .fill( '/checkout' ); - await page.keyboard.press( 'Enter' ); - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ checkoutBlockPageTitle } is now live.` ) - ).toBeVisible(); + await goToPageEditor( { page } ); + await fillPageTitle( page, checkoutBlockPageTitle ); + await insertBlockByShortcut( page, '/checkout' ); + await publishPage( page, checkoutBlockPageTitle ); } ); test( 'that inclusive tax is displayed properly in blockbased Cart & Checkout pages', async ( { page, + context, } ) => { + await context.clearCookies(); + + await addAProductToCart( page, productId ); + await test.step( 'Load cart page and confirm price display', async () => { await page.goto( cartBlockPageSlug ); await expect( @@ -212,6 +171,7 @@ test.describe( 'Shopper Cart & Checkout Block Tax Display', () => { test( 'that exclusive tax is displayed properly in blockbased Cart & Checkout pages', async ( { page, baseURL, + context, } ) => { const api = new wcApi( { url: baseURL, @@ -223,6 +183,9 @@ test.describe( 'Shopper Cart & Checkout Block Tax Display', () => { value: 'excl', } ); + await context.clearCookies(); + await addAProductToCart( page, productId ); + await test.step( 'Load cart page and confirm price display', async () => { await page.goto( cartBlockPageSlug ); await expect( @@ -329,15 +292,9 @@ test.describe( 'Shopper Cart & Checkout Block Tax Rounding', () => { await context.clearCookies(); // all tests use the same products - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); - await page.goto( `/shop/?add-to-cart=${ productId2 }`, { - waitUntil: 'networkidle', - } ); - await page.goto( `/shop/?add-to-cart=${ productId2 }`, { - waitUntil: 'networkidle', - } ); + await addAProductToCart( page, productId ); + await addAProductToCart( page, productId2 ); + await addAProductToCart( page, productId2 ); } ); test.afterAll( async ( { baseURL } ) => { @@ -624,9 +581,7 @@ test.describe( 'Shopper Cart & Checkout Block Tax Levels', () => { await context.clearCookies(); // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await addAProductToCart( page, productId ); } ); test.afterAll( async ( { baseURL } ) => { @@ -896,9 +851,7 @@ test.describe( 'Shipping Cart & Checkout Block Tax', () => { await context.clearCookies(); // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await addAProductToCart( page, productId ); } ); test.afterAll( async ( { baseURL } ) => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-calculate-tax.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-calculate-tax.spec.js index 145ab55de1294..146b414fc816e 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-calculate-tax.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-calculate-tax.spec.js @@ -1,11 +1,13 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const { customer } = require( '../../test-data/data' ); +const { addAProductToCart } = require( '../../utils/cart' ); +const { random } = require( '../../utils/helpers' ); -const productName = `Taxed products are awesome ${ Date.now() }`; +const productName = `Taxed products are awesome ${ random() }`; const productPrice = '200.00'; const messyProductPrice = '13.47'; -const secondProductName = `Other products are also awesome ${ Date.now() }`; +const secondProductName = `Other products are also awesome ${ random() }`; let productId, productId2, @@ -92,11 +94,8 @@ test.describe.serial( 'Tax rates in the cart and checkout', () => { test.beforeEach( async ( { page, context } ) => { // Shopping cart is very sensitive to cookies, so be explicit await context.clearCookies(); - // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await addAProductToCart( page, productId ); } ); test.afterAll( async ( { baseURL } ) => { @@ -131,7 +130,7 @@ test.describe.serial( 'Tax rates in the cart and checkout', () => { value: 'incl', } ); - await test.step( 'Load shop page and confirm price display', async () => { + await test.step( 'Load shop page, confirm title and confirm price display', async () => { await page.goto( '/shop/' ); await expect( page.getByRole( 'heading', { name: 'Shop' } ) @@ -140,6 +139,9 @@ test.describe.serial( 'Tax rates in the cart and checkout', () => { await expect( page.getByText( '$250.00', { exact: true } ) ).toBeVisible(); + expect( await page.title() ).toBe( + 'Shop – WooCommerce Core E2E Test Suite' + ); } ); await test.step( 'Load cart page and confirm price display', async () => { @@ -375,16 +377,9 @@ test.describe.serial( 'Tax rates in the cart and checkout', () => { // Shopping cart is very sensitive to cookies, so be explicit await context.clearCookies(); - // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); - await page.goto( `/shop/?add-to-cart=${ productId2 }`, { - waitUntil: 'networkidle', - } ); - await page.goto( `/shop/?add-to-cart=${ productId2 }`, { - waitUntil: 'networkidle', - } ); + await addAProductToCart( page, productId ); + await addAProductToCart( page, productId2 ); + await addAProductToCart( page, productId2 ); } ); test.afterAll( async ( { baseURL } ) => { @@ -604,11 +599,8 @@ test.describe.serial( 'Tax rates in the cart and checkout', () => { test.beforeEach( async ( { page, context } ) => { // Shopping cart is very sensitive to cookies, so be explicit await context.clearCookies(); - // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await addAProductToCart( page, productId ); } ); test.afterAll( async ( { baseURL } ) => { @@ -896,11 +888,8 @@ test.describe.serial( 'Tax rates in the cart and checkout', () => { test.beforeEach( async ( { page, context } ) => { // Shopping cart is very sensitive to cookies, so be explicit await context.clearCookies(); - // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await addAProductToCart( page, productId ); } ); test.afterAll( async ( { baseURL } ) => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-coupons.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-coupons.spec.js index 289c51512d817..52fd1a839df1f 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-coupons.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-coupons.spec.js @@ -1,3 +1,4 @@ +const { addAProductToCart } = require( '../../utils/cart' ); const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; @@ -64,7 +65,7 @@ test.describe( 'Cart & Checkout applying coupons', () => { } ); } ); - test.beforeEach( async ( { page, context } ) => { + test.beforeEach( async ( { context } ) => { // Shopping cart is very sensitive to cookies, so be explicit await context.clearCookies(); } ); @@ -91,8 +92,7 @@ test.describe( 'Cart & Checkout applying coupons', () => { context, } ) => { await test.step( 'Load cart page and apply coupons', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page.locator( '#coupon_code' ).fill( coupons[ i ].code ); @@ -116,10 +116,9 @@ test.describe( 'Cart & Checkout applying coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and apply coupons', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); - await page.goto( '/checkout/', { waitUntil: 'networkidle' } ); + await page.goto( '/checkout' ); await page .locator( 'text=Click here to enter your code' ) .click(); @@ -144,8 +143,7 @@ test.describe( 'Cart & Checkout applying coupons', () => { context, } ) => { await test.step( 'Load cart page and try applying same coupon twice', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page.locator( '#coupon_code' ).fill( coupons[ 0 ].code ); @@ -154,12 +152,12 @@ test.describe( 'Cart & Checkout applying coupons', () => { await expect( page.getByText( 'Coupon code applied successfully.' ) ).toBeVisible(); - await page.waitForLoadState( 'networkidle' ); + // try to apply the same coupon await page.goto( '/cart/' ); await page.locator( '#coupon_code' ).fill( coupons[ 0 ].code ); await page.getByRole( 'button', { name: 'Apply coupon' } ).click(); - await page.waitForLoadState( 'networkidle' ); + // error received await expect( page.getByText( 'Coupon code already applied!' ) @@ -176,8 +174,7 @@ test.describe( 'Cart & Checkout applying coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try applying same coupon twice', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); await page.locator( 'text=Click here to enter your code' ).click(); @@ -207,8 +204,7 @@ test.describe( 'Cart & Checkout applying coupons', () => { test( 'allows applying multiple coupons', async ( { page, context } ) => { await test.step( 'Load cart page and try applying multiple coupons', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page.locator( '#coupon_code' ).fill( coupons[ 0 ].code ); @@ -218,7 +214,9 @@ test.describe( 'Cart & Checkout applying coupons', () => { page.getByText( 'Coupon code applied successfully.' ) ).toBeVisible(); - await page.waitForLoadState( 'networkidle' ); + // If not waiting the next coupon is not applied correctly. This should be temporary, we need a better way to handle this. + await page.waitForTimeout( 2000 ); + await page.locator( '#coupon_code' ).fill( coupons[ 2 ].code ); await page.getByRole( 'button', { name: 'Apply coupon' } ).click(); // successful @@ -240,8 +238,7 @@ test.describe( 'Cart & Checkout applying coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try applying multiple coupons', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); await page.locator( 'text=Click here to enter your code' ).click(); @@ -251,6 +248,10 @@ test.describe( 'Cart & Checkout applying coupons', () => { await expect( page.getByText( 'Coupon code applied successfully.' ) ).toBeVisible(); + + // If not waiting the next coupon is not applied correctly. This should be temporary, we need a better way to handle this. + await page.waitForTimeout( 2000 ); + await page.locator( 'text=Click here to enter your code' ).click(); await page.locator( '#coupon_code' ).fill( coupons[ 2 ].code ); await page.locator( 'text=Apply coupon' ).click(); @@ -276,8 +277,7 @@ test.describe( 'Cart & Checkout applying coupons', () => { context, } ) => { await test.step( 'Load cart page and try restoring total when removed coupons', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page.locator( '#coupon_code' ).fill( coupons[ 0 ].code ); @@ -292,7 +292,6 @@ test.describe( 'Cart & Checkout applying coupons', () => { ).toContainText( totals[ 0 ] ); await page.locator( 'a.woocommerce-remove-coupon' ).click(); - await page.waitForLoadState( 'networkidle' ); await expect( page.locator( '.order-total .amount' ) @@ -302,8 +301,7 @@ test.describe( 'Cart & Checkout applying coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try restoring total when removed coupons', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); await page.locator( 'text=Click here to enter your code' ).click(); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-restricted-coupons.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-restricted-coupons.spec.js index 07d61f329d7b7..e80a49762ce21 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-restricted-coupons.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-checkout-restricted-coupons.spec.js @@ -1,5 +1,6 @@ const { test, expect } = require( '@playwright/test' ); const { getOrderIdFromUrl } = require( '../../utils/order' ); +const { addAProductToCart } = require( '../../utils/cart' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const includedProductName = 'Included test product'; @@ -185,8 +186,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { test( 'expired coupon cannot be used', async ( { page, context } ) => { await test.step( 'Load cart page and try expired coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page @@ -201,8 +201,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try expired coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); await page @@ -223,8 +222,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { context, } ) => { await test.step( 'Load cart page and try limited coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page @@ -238,8 +236,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { // add a couple more in order to hit minimum spend for ( let i = 0; i < 2; i++ ) { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); } // passed because we're between 50 and 200 dollars await page.goto( '/cart/' ); @@ -267,8 +264,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try limited coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); await page @@ -285,8 +281,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { // add a couple more in order to hit minimum spend for ( let i = 0; i < 2; i++ ) { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); } // passed because we're between 50 and 200 dollars await page.goto( '/checkout/' ); @@ -320,8 +315,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { test( 'coupon cannot be used on sale item', async ( { page, context } ) => { await test.step( 'Load cart page and try coupon usage on sale item', async () => { - await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, secondProductId ); await page.goto( '/cart/' ); await page @@ -339,8 +333,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try coupon usage on sale item', async () => { - await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, secondProductId ); await page.goto( '/checkout/' ); await page @@ -400,8 +393,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { } await test.step( 'Load cart page and try over limit coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page @@ -419,8 +411,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try over limit coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); await page @@ -448,8 +439,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { context, } ) => { await test.step( 'Load cart page and try included certain items coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, secondProductId ); await page.goto( '/cart/' ); await page @@ -467,8 +457,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try included certain items coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, secondProductId ); await page.goto( '/checkout/' ); await page @@ -492,8 +481,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { context, } ) => { await test.step( 'Load cart page and try on certain products coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page @@ -509,8 +497,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try on certain products coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); await page @@ -532,8 +519,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { context, } ) => { await test.step( 'Load cart page and try excluded items coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, secondProductId ); await page.goto( '/cart/' ); await page @@ -551,8 +537,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try excluded items coupon usage', async () => { - await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, secondProductId ); await page.goto( '/checkout/' ); await page @@ -576,8 +561,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { context, } ) => { await test.step( 'Load cart page and try coupon usage on other items', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page @@ -593,8 +577,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { await context.clearCookies(); await test.step( 'Load checkout page and try coupon usage on other items', async () => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); await page @@ -614,8 +597,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { test( 'coupon cannot be used by any customer on cart (email restricted)', async ( { page, } ) => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/cart/' ); await page.getByPlaceholder( 'Coupon code' ).fill( 'email-restricted' ); @@ -631,8 +613,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { test( 'coupon cannot be used by any customer on checkout (email restricted)', async ( { page, } ) => { - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); @@ -656,8 +637,6 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { await page.getByPlaceholder( 'Coupon code' ).fill( 'email-restricted' ); await page.getByRole( 'button', { name: 'Apply coupon' } ).click(); - await page.waitForLoadState( 'networkidle' ); - await expect( page.getByText( 'Please enter a valid email to use coupon code "email-restricted".' @@ -676,8 +655,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { version: 'wc/v3', } ); - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); @@ -712,8 +690,7 @@ test.describe( 'Cart & Checkout Restricted Coupons', () => { const newOrderId = getOrderIdFromUrl( page ); // try to order a second time, but should get an error - await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, firstProductId ); await page.goto( '/checkout/' ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-redirection.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-redirection.spec.js index f83ba3c73249b..a153436e948e7 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-redirection.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart-redirection.spec.js @@ -58,7 +58,6 @@ test.describe( 'Cart > Redirect to cart from shop', () => { await page .locator( `a[data-product_id='${ productId }'][href*=add-to-cart]` ) .click(); - await page.waitForLoadState( 'networkidle' ); await expect( page ).toHaveURL( /.*\/cart/ ); await expect( page.locator( 'td.product-name' ) ).toContainText( @@ -69,7 +68,6 @@ test.describe( 'Cart > Redirect to cart from shop', () => { test( 'can redirect user to cart from detail page', async ( { page } ) => { await page.goto( '/shop/' ); await page.locator( `text=${ productName }` ).click(); - await page.waitForLoadState( 'networkidle' ); await page.getByRole( 'button', { name: 'Add to cart' } ).click(); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart.spec.js index 9e2336f88ea2a..084f120cebbd2 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/cart.spec.js @@ -1,7 +1,7 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; -const productName = 'Cart product test'; +const productName = `Cart product test ${ Date.now() }`; const productPrice = '13.99'; const twoProductPrice = +productPrice * 2; const fourProductPrice = +productPrice * 4; @@ -72,8 +72,13 @@ test.describe( 'Cart page', () => { async function goToShopPageAndAddProductToCart( page, prodName ) { await page.goto( '/shop/?orderby=date' ); - await page.getByLabel( `Add to cart: “${ prodName }”` ).click(); - await page.waitForLoadState( 'networkidle' ); + const responsePromise = page.waitForResponse( + '**/wp-json/wc/store/v1/batch?**' + ); + await page + .getByLabel( `Add to cart: “${ prodName }”`, { exact: true } ) + .click(); + await responsePromise; } test( 'should display no item in the cart', async ( { page } ) => { @@ -204,17 +209,22 @@ test.describe( 'Cart page', () => { await expect( page.locator( '.cross-sells' ) ).toContainText( 'You may be interested in…' ); + await page .getByLabel( `Add to cart: “${ productName } cross-sell 1”` ) .click(); + await expect( + page.getByLabel( `Remove ${ productName } cross-sell 1 from cart` ) + ).toBeVisible(); await page .getByLabel( `Add to cart: “${ productName } cross-sell 2”` ) .click(); - await page.waitForLoadState( 'networkidle' ); + await expect( + page.getByLabel( `Remove ${ productName } cross-sell 2 from cart` ) + ).toBeVisible(); // reload page and confirm added products - await page.reload(); - await page.waitForLoadState( 'networkidle' ); + await page.reload( { waitUntil: 'domcontentloaded' } ); await expect( page.locator( '.cross-sells' ) ).toBeHidden(); await expect( page.locator( '.order-total .amount' ) ).toContainText( `$${ fourProductPrice }` diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block-coupons.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block-coupons.spec.js index c0ac4767a92a3..11d47c046a0ce 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block-coupons.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block-coupons.spec.js @@ -1,9 +1,14 @@ -const { test, expect } = require( '@playwright/test' ); -const { admin } = require( '../../test-data/data' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); -const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; - -const simpleProductName = 'Checkout Coupons Product'; +const { + goToPageEditor, + fillPageTitle, + insertBlockByShortcut, + publishPage, +} = require( '../../utils/editor' ); +const { addAProductToCart } = require( '../../utils/cart' ); +const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); +const { random } = require( '../../utils/helpers' ); + +const simpleProductName = `Checkout Coupons Product ${ random() }`; const singleProductFullPrice = '110.00'; const singleProductSalePrice = '55.00'; const coupons = [ @@ -28,23 +33,33 @@ const customerBilling = { email: 'john.doe.merchant.test@example.com', }; -const checkoutBlockPageTitle = 'Checkout Block'; -const checkoutBlockPageSlug = checkoutBlockPageTitle - .replace( / /gi, '-' ) - .toLowerCase(); - let productId, orderId, limitedCouponId; -test.describe( 'Checkout Block Applying Coupons', () => { +baseTest.describe( 'Checkout Block Applying Coupons', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + testPageTitlePrefix: 'Checkout Block', + page: async ( { context, page, testPage }, use ) => { + await goToPageEditor( { page } ); + await fillPageTitle( page, testPage.title ); + await insertBlockByShortcut( page, '/checkout' ); + await publishPage( page, testPage.title ); + + await context.clearCookies(); + + await addAProductToCart( page, productId ); + await page.goto( testPage.slug ); + await expect( + page.getByRole( 'heading', { name: testPage.title } ) + ).toBeVisible(); + + await use( page ); + }, + } ); + const couponBatchId = []; - test.beforeAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.beforeAll( async ( { api } ) => { // make sure the currency is USD await api.put( 'settings/general/woocommerce_currency', { value: 'USD', @@ -98,13 +113,7 @@ test.describe( 'Checkout Block Applying Coupons', () => { } ); } ); - test.afterAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.afterAll( async ( { api } ) => { await api.post( 'products/batch', { delete: [ productId ], } ); @@ -116,53 +125,10 @@ test.describe( 'Checkout Block Applying Coupons', () => { } ); } ); - test.beforeEach( async ( { context } ) => { - // Shopping cart is very sensitive to cookies, so be explicit - await context.clearCookies(); - } ); - - test( 'can create checkout block page', async ( { page } ) => { - // create a new page with checkout block - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - await page.locator( 'input[name="log"]' ).fill( admin.username ); - await page.locator( 'input[name="pwd"]' ).fill( admin.password ); - await page.locator( 'text=Log In' ).click(); - - await disableWelcomeModal( { page } ); - - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( checkoutBlockPageTitle ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); - await page - .getByRole( 'document', { - name: 'Empty block; start writing or type forward slash to choose a block', - } ) - .fill( '/checkout' ); - await page.keyboard.press( 'Enter' ); - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ checkoutBlockPageTitle } is now live.` ) - ).toBeVisible(); - } ); - test( 'allows checkout block to apply coupon of any type', async ( { page, } ) => { const totals = [ '$50.00', '$27.50', '$45.00' ]; - // add product to cart block and go to checkout - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); - await page.goto( checkoutBlockPageSlug ); - await expect( - page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) - ).toBeVisible(); // apply all coupon types for ( let i = 0; i < coupons.length; i++ ) { @@ -202,13 +168,6 @@ test.describe( 'Checkout Block Applying Coupons', () => { const totals = [ '$50.00', '$22.50', '$12.50' ]; const totalsReverse = [ '$17.50', '$45.00', '$55.00' ]; const discounts = [ '-$5.00', '-$32.50', '-$42.50' ]; - // add product to cart block and go to checkout - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); - await page.goto( checkoutBlockPageSlug ); - await expect( - page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) - ).toBeVisible(); // add all coupons and verify prices for ( let i = 0; i < coupons.length; i++ ) { @@ -251,14 +210,6 @@ test.describe( 'Checkout Block Applying Coupons', () => { test( 'prevents checkout block applying same coupon twice', async ( { page, } ) => { - // add product to cart block and go to checkout - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); - await page.goto( checkoutBlockPageSlug ); - await expect( - page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) - ).toBeVisible(); - // try to add two same coupons and verify the error message await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page @@ -289,14 +240,6 @@ test.describe( 'Checkout Block Applying Coupons', () => { test( 'prevents checkout block applying coupon with usage limit', async ( { page, } ) => { - // add product to cart block and go to checkout - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); - await page.goto( checkoutBlockPageSlug ); - await expect( - page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) - ).toBeVisible(); - // add coupon with usage limit await page.getByRole( 'button', { name: 'Add a coupon' } ).click(); await page diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block.spec.js index fe4bffb05351f..f489067ebaf2e 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-block.spec.js @@ -2,13 +2,21 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const { admin, customer } = require( '../../test-data/data' ); const { setFilterValue, clearFilters } = require( '../../utils/filters' ); -const { addProductsToCart } = require( '../../utils/pdp' ); +const { + addProductsToCart: pdpAddProductsToCart, +} = require( '../../utils/pdp' ); +const { addAProductToCart } = require( '../../utils/cart' ); const { fillShippingCheckoutBlocks, fillBillingCheckoutBlocks, } = require( '../../utils/checkout' ); const { getOrderIdFromUrl } = require( '../../utils/order' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); +const { + goToPageEditor, + fillPageTitle, + insertBlockByShortcut, + publishPage, +} = require( '../../utils/editor' ); const guestEmail = 'checkout-guest@example.com'; const newAccountEmail = 'marge-test-account@example.com'; @@ -20,7 +28,7 @@ const singleProductSalePrice = '75.00'; const twoProductPrice = ( singleProductSalePrice * 2 ).toString(); const threeProductPrice = ( singleProductSalePrice * 3 ).toString(); -const checkoutBlockPageTitle = 'Checkout Block'; +const checkoutBlockPageTitle = `Checkout Block ${ Date.now() }`; const checkoutBlockPageSlug = checkoutBlockPageTitle .replace( / /gi, '-' ) .toLowerCase(); @@ -33,6 +41,8 @@ let guestOrderId1, shippingZoneId; test.describe( 'Checkout Block page', () => { + test.use( { storageState: process.env.ADMINSTATE } ); + test.beforeAll( async ( { baseURL } ) => { const api = new wcApi( { url: baseURL, @@ -205,40 +215,12 @@ test.describe( 'Checkout Block page', () => { } ); } ); - test.beforeEach( async ( { context } ) => { - // Shopping cart is very sensitive to cookies, so be explicit - await context.clearCookies(); - } ); - test( 'can see empty checkout block page', async ( { page } ) => { // create a new page with checkout block - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - await page.locator( 'input[name="log"]' ).fill( admin.username ); - await page.locator( 'input[name="pwd"]' ).fill( admin.password ); - await page.locator( 'text=Log In' ).click(); - - await disableWelcomeModal( { page } ); - - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( checkoutBlockPageTitle ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); - await page - .getByRole( 'document', { - name: 'Empty block; start writing or type forward slash to choose a block', - } ) - .fill( '/checkout' ); - await page.keyboard.press( 'Enter' ); - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ checkoutBlockPageTitle } is now live.` ) - ).toBeVisible(); + await goToPageEditor( { page } ); + await fillPageTitle( page, checkoutBlockPageTitle ); + await insertBlockByShortcut( page, '/checkout' ); + await publishPage( page, checkoutBlockPageTitle ); // go to the page to test empty cart block await page.goto( checkoutBlockPageSlug ); @@ -259,9 +241,12 @@ test.describe( 'Checkout Block page', () => { test( 'allows customer to choose available payment methods', async ( { page, + context, } ) => { + await context.clearCookies(); + // this time we're going to add two products to the cart - await addProductsToCart( page, simpleProductName, '2' ); + await pdpAddProductsToCart( page, simpleProductName, '2' ); await page.goto( checkoutBlockPageSlug ); await expect( @@ -277,9 +262,6 @@ test.describe( 'Checkout Block page', () => { '.wc-block-components-order-summary-item__individual-price' ) ).toContainText( `$${ singleProductSalePrice }` ); - await expect( - page.locator( '.wc-block-components-product-metadata__description' ) - ).toContainText( simpleProductDesc ); await expect( page.locator( '.wc-block-components-totals-footer-item > .wc-block-components-totals-item__value' @@ -293,9 +275,14 @@ test.describe( 'Checkout Block page', () => { await expect( page.getByLabel( 'Cash on delivery' ) ).toBeChecked(); } ); - test( 'allows customer to fill shipping details', async ( { page } ) => { + test( 'allows customer to fill shipping details', async ( { + page, + context, + } ) => { + await context.clearCookies(); + // this time we're going to add three products to the cart - await addProductsToCart( page, simpleProductName, '3' ); + await pdpAddProductsToCart( page, simpleProductName, '3' ); await page.goto( checkoutBlockPageSlug ); await expect( @@ -333,16 +320,22 @@ test.describe( 'Checkout Block page', () => { test( 'allows customer to fill different shipping and billing details', async ( { page, + context, } ) => { - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await context.clearCookies(); + + await addAProductToCart( page, productId ); await page.goto( checkoutBlockPageSlug ); await expect( page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) ).toBeVisible(); + // For flakiness, sometimes the email address is not filled + await page.getByLabel( 'Email address' ).click(); await page.getByLabel( 'Email address' ).fill( guestEmail ); + await expect( page.getByLabel( 'Email address' ) ).toHaveValue( + guestEmail + ); // fill shipping address await fillShippingCheckoutBlocks( page ); @@ -369,10 +362,7 @@ test.describe( 'Checkout Block page', () => { // get order ID from the page guestOrderId2 = getOrderIdFromUrl( page ); - // go again to the checkout to verify details - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await addAProductToCart( page, productId ); await page.goto( checkoutBlockPageSlug ); await expect( page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) @@ -441,16 +431,22 @@ test.describe( 'Checkout Block page', () => { test( 'allows customer to fill shipping details and toggle different billing', async ( { page, + context, } ) => { - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await context.clearCookies(); + + await addAProductToCart( page, productId ); await page.goto( checkoutBlockPageSlug ); await expect( page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) ).toBeVisible(); + // For flakiness, sometimes the email address is not filled + await page.getByLabel( 'Email address' ).click(); await page.getByLabel( 'Email address' ).fill( customer.email ); + await expect( page.getByLabel( 'Email address' ) ).toHaveValue( + customer.email + ); // fill shipping address and check the toggle to use a different address for billing await fillShippingCheckoutBlocks( page ); @@ -468,16 +464,22 @@ test.describe( 'Checkout Block page', () => { test( 'can choose different shipping types in the checkout', async ( { page, + context, } ) => { - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await context.clearCookies(); + + await addAProductToCart( page, productId ); await page.goto( checkoutBlockPageSlug ); await expect( page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) ).toBeVisible(); + // For flakiness, sometimes the email address is not filled + await page.getByLabel( 'Email address' ).click(); await page.getByLabel( 'Email address' ).fill( customer.email ); + await expect( page.getByLabel( 'Email address' ) ).toHaveValue( + customer.email + ); // fill shipping address await fillShippingCheckoutBlocks( page ); @@ -543,15 +545,25 @@ test.describe( 'Checkout Block page', () => { ).toContainText( twoProductPrice ); } ); - test( 'allows guest customer to place an order', async ( { page } ) => { - await addProductsToCart( page, simpleProductName, '2' ); + test( 'allows guest customer to place an order', async ( { + page, + context, + } ) => { + await context.clearCookies(); + + await pdpAddProductsToCart( page, simpleProductName, '2' ); await page.goto( checkoutBlockPageSlug ); await expect( page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) ).toBeVisible(); + // For flakiness, sometimes the email address is not filled + await page.getByLabel( 'Email address' ).click(); await page.getByLabel( 'Email address' ).fill( guestEmail ); + await expect( page.getByLabel( 'Email address' ) ).toHaveValue( + guestEmail + ); // fill shipping address and check cash on delivery method await fillShippingCheckoutBlocks( page ); @@ -614,7 +626,12 @@ test.describe( 'Checkout Block page', () => { ).toBeVisible(); // However if they supply the *correct* billing email address, they should see the order received page again. + // For flakiness, sometimes the email address is not filled + await page.getByLabel( 'Email address' ).click(); await page.getByLabel( 'Email address' ).fill( guestEmail ); + await expect( page.getByLabel( 'Email address' ) ).toHaveValue( + guestEmail + ); await page.getByRole( 'button', { name: /Verify|Confirm/ } ).click(); await expect( page.getByText( 'Your order has been received' ) @@ -650,8 +667,13 @@ test.describe( 'Checkout Block page', () => { await clearFilters( page ); } ); - test( 'allows existing customer to place an order', async ( { page } ) => { - await addProductsToCart( page, simpleProductName, '2' ); + test( 'allows existing customer to place an order', async ( { + page, + context, + } ) => { + await context.clearCookies(); + + await pdpAddProductsToCart( page, simpleProductName, '2' ); await page.goto( checkoutBlockPageSlug ); await expect( @@ -660,7 +682,6 @@ test.describe( 'Checkout Block page', () => { // click to log in and make sure you are on the same page after logging in await page.locator( 'text=Log in.' ).click(); - await page.waitForLoadState( 'networkidle' ); await page .locator( 'input[name="username"]' ) .fill( customer.username ); @@ -668,7 +689,6 @@ test.describe( 'Checkout Block page', () => { .locator( 'input[name="password"]' ) .fill( customer.password ); await page.locator( 'text=Log in' ).click(); - await page.waitForLoadState( 'networkidle' ); await expect( page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) ).toBeVisible(); @@ -751,10 +771,13 @@ test.describe( 'Checkout Block page', () => { ); } ); - test( 'can create an account during checkout', async ( { page } ) => { - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + test( 'can create an account during checkout', async ( { + page, + context, + } ) => { + await context.clearCookies(); + + await addAProductToCart( page, productId ); await page.goto( checkoutBlockPageSlug ); await expect( page.getByRole( 'heading', { name: checkoutBlockPageTitle } ) @@ -765,7 +788,12 @@ test.describe( 'Checkout Block page', () => { await page.getByLabel( 'Create an account?' ).check(); await expect( page.getByLabel( 'Create an account?' ) ).toBeChecked(); + // For flakiness, sometimes the email address is not filled + await page.getByLabel( 'Email address' ).click(); await page.getByLabel( 'Email address' ).fill( newAccountEmail ); + await expect( page.getByLabel( 'Email address' ) ).toHaveValue( + newAccountEmail + ); // fill shipping address and check cash on delivery method await fillShippingCheckoutBlocks( page, 'Marge' ); @@ -801,7 +829,6 @@ test.describe( 'Checkout Block page', () => { // sign in as admin to confirm account creation await page.goto( 'wp-admin/users.php' ); - await page.waitForLoadState( 'networkidle' ); await page.locator( 'input[name="log"]' ).fill( admin.username ); await page.locator( 'input[name="pwd"]' ).fill( admin.password ); await page.locator( 'text=Log in' ).click(); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-create-account.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-create-account.spec.js index acc21a056a45e..76f1d0aac162c 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-create-account.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-create-account.spec.js @@ -2,6 +2,7 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const { admin } = require( '../../test-data/data' ); const { getOrderIdFromUrl } = require( '../../utils/order' ); +const { addAProductToCart } = require( '../../utils/cart' ); const billingEmail = 'marge-test-account@example.com'; @@ -132,8 +133,7 @@ test.describe( 'Shopper Checkout Create Account', () => { await context.clearCookies(); // all tests use the first product - await page.goto( `shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, productId ); } ); test( 'can create an account during checkout', async ( { page } ) => { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-login.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-login.spec.js index 9dd51ea473fbb..4595ad180e4d1 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-login.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-login.spec.js @@ -1,5 +1,6 @@ const { test, expect } = require( '@playwright/test' ); const { getOrderIdFromUrl } = require( '../../utils/order' ); +const { addAProductToCart } = require( '../../utils/cart' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const customer = { @@ -112,8 +113,7 @@ test.describe( 'Shopper Checkout Login Account', () => { await context.clearCookies(); // all tests use the first product - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, productId ); } ); test( 'can login to an existing account during checkout', async ( { diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout.spec.js index dd76a3b442a9e..c08a622b73167 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout.spec.js @@ -3,6 +3,7 @@ const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const { admin, customer } = require( '../../test-data/data' ); const { setFilterValue, clearFilters } = require( '../../utils/filters' ); const { addProductsToCart } = require( '../../utils/pdp' ); +const { addAProductToCart } = require( '../../utils/cart' ); const { getOrderIdFromUrl } = require( '../../utils/order' ); const guestEmail = 'checkout-guest@example.com'; @@ -119,8 +120,7 @@ test.describe( 'Checkout page', () => { } ); test( 'should display cart items in order review', async ( { page } ) => { - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, productId ); await page.goto( '/checkout/' ); @@ -201,9 +201,7 @@ test.describe( 'Checkout page', () => { test( 'warn when customer is missing required details', async ( { page, } ) => { - await page.goto( `/shop/?add-to-cart=${ productId }`, { - waitUntil: 'networkidle', - } ); + await addAProductToCart( page, productId ); await page.goto( '/checkout/' ); @@ -450,7 +448,12 @@ test.describe( 'Checkout page', () => { .locator( 'input[name="password"]' ) .fill( customer.password ); await page.locator( 'text=Log In' ).click(); - await page.waitForLoadState( 'networkidle' ); + await expect( + page.getByText( + `Hello ${ customer.first_name } ${ customer.last_name }` + ) + ).toBeVisible(); + await addProductsToCart( page, simpleProductName, '2' ); await page.goto( '/checkout/' ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/mini-cart.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/mini-cart.spec.js index 0eb3f9442928c..e64fb431b5694 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/mini-cart.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/mini-cart.spec.js @@ -1,8 +1,9 @@ const { test, expect } = require( '@playwright/test' ); const { disableWelcomeModal } = require( '../../utils/editor' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; +const { random } = require( '../../utils/helpers' ); -const miniCartPageTitle = `Mini Cart ${ Date.now() }`; +const miniCartPageTitle = `Mini Cart ${ random() }`; const miniCartPageSlug = miniCartPageTitle.replace( / /gi, '-' ).toLowerCase(); const miniCartButton = 'main .wc-block-mini-cart__button'; const miniCartBadge = 'main .wc-block-mini-cart__badge'; diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/order-email-receiving.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/order-email-receiving.spec.js index 627995bd610f3..dc6e325f7d36e 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/order-email-receiving.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/order-email-receiving.spec.js @@ -2,6 +2,7 @@ const { test, expect } = require( '@playwright/test' ); const { customer, storeDetails } = require( '../../test-data/data' ); const { api } = require( '../../utils' ); const { getOrderIdFromUrl } = require( '../../utils/order' ); +const { addAProductToCart } = require( '../../utils/cart' ); let productId, orderId, zoneId; @@ -68,8 +69,7 @@ test.describe( 'Shopper Order Email Receiving', () => { // ensure that the store's address is in the US await api.update.storeDetails( storeDetails.us.store ); - await page.goto( `/shop/?add-to-cart=${ productId }` ); - await page.waitForLoadState( 'networkidle' ); + await addAProductToCart( page, productId ); await page.goto( '/checkout/' ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/product-simple.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/product-simple.spec.js index 76252a79e105c..8d3d3ab9fd758 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/product-simple.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/product-simple.spec.js @@ -1,5 +1,3 @@ -/* eslint-disable playwright/no-networkidle */ -/* eslint-disable jest/valid-expect */ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; @@ -122,6 +120,11 @@ test.describe( 'Single Product Page', () => { test( 'should be able to see product description', async ( { page } ) => { await page.goto( `product/${ simpleProductSlug }` ); + + expect( await page.title() ).toBe( + simpleProductName + ' – WooCommerce Core E2E Test Suite' + ); + await page.getByRole( 'tab', { name: 'Description' } ).click(); await expect( diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-products-filter-by-price.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-products-filter-by-price.spec.js index dceb19c20cc9c..65bee2982e19b 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-products-filter-by-price.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-products-filter-by-price.spec.js @@ -1,6 +1,11 @@ -const { test, expect } = require( '@playwright/test' ); -const { disableWelcomeModal } = require( '../../utils/editor' ); -const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; +const { test: baseTest, expect } = require( '../../fixtures/fixtures' ); +const { + goToPageEditor, + fillPageTitle, + insertBlock, + insertBlockByShortcut, + publishPage, +} = require( '../../utils/editor' ); const singleProductPrice1 = '10'; const singleProductPrice2 = '50'; @@ -8,23 +13,15 @@ const singleProductPrice3 = '200'; const simpleProductName = 'AAA Filter Products'; -const productsFilteringPageTitle = `Products Filtering ${ Date.now() }`; -const productsFilteringPageSlug = productsFilteringPageTitle - .replace( / /gi, '-' ) - .toLowerCase(); - let product1Id, product2Id, product3Id; -test.describe( 'Filter items in the shop by product price', () => { - test.use( { storageState: process.env.ADMINSTATE } ); +baseTest.describe( 'Filter items in the shop by product price', () => { + const test = baseTest.extend( { + storageState: process.env.ADMINSTATE, + testPageTitlePrefix: 'Products filter', + } ); - test.beforeAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.beforeAll( async ( { api } ) => { // add products await api .post( 'products', { @@ -55,13 +52,7 @@ test.describe( 'Filter items in the shop by product price', () => { } ); } ); - test.afterAll( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', - } ); + test.afterAll( async ( { api } ) => { await api.post( 'products/batch', { delete: [ product1Id, product2Id, product3Id ], } ); @@ -69,53 +60,20 @@ test.describe( 'Filter items in the shop by product price', () => { test( 'filter products by prices on the created page', async ( { page, + testPage, } ) => { const sortingProductsDropdown = '.wc-block-sort-select__select'; - // go to create a new page with filtering products by price - await page.goto( 'wp-admin/post-new.php?post_type=page' ); - - await disableWelcomeModal( { page } ); - - await page - .getByRole( 'textbox', { name: 'Add title' } ) - .fill( productsFilteringPageTitle ); - await page.getByRole( 'button', { name: 'Add default block' } ).click(); - await page - .getByRole( 'document', { - name: 'Empty block; start writing or type forward slash to choose a block', - } ) - .fill( '/filter' ); - await page - .getByRole( 'option' ) - .filter( { hasText: 'Filter by Price' } ) - .click(); - - await page.getByRole( 'textbox', { name: 'Add title' } ).click(); - await page.getByLabel( 'Add block' ).click(); - await page - .getByPlaceholder( 'Search', { exact: true } ) - .fill( 'products' ); - await page - .getByRole( 'option' ) - .filter( { hasText: 'All Products' } ) - .click(); - - await page - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await page - .getByRole( 'region', { name: 'Editor publish' } ) - .getByRole( 'button', { name: 'Publish', exact: true } ) - .click(); - await expect( - page.getByText( `${ productsFilteringPageTitle } is now live.` ) - ).toBeVisible(); + await goToPageEditor( { page } ); + await fillPageTitle( page, testPage.title ); + await insertBlockByShortcut( page, '/filter' ); + await insertBlock( page, 'All Products' ); + await publishPage( page, testPage.title ); // go to the page to test filtering products by price - await page.goto( productsFilteringPageSlug ); + await page.goto( testPage.slug ); await expect( - page.getByRole( 'heading', { name: productsFilteringPageTitle } ) + page.getByRole( 'heading', { name: testPage.title } ) ).toBeVisible(); // The price filter input is initially enabled, but it becomes disabled diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-title-after-deletion.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-title-after-deletion.spec.js new file mode 100644 index 0000000000000..3f5ec63cab34f --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/shop-title-after-deletion.spec.js @@ -0,0 +1,38 @@ +const { test, expect } = require( '@playwright/test' ); + +// test case for bug https://github.com/woocommerce/woocommerce/pull/46429 +test.describe( 'Check the title of the shop page after the page has been deleted', () => { + test.use( { storageState: process.env.ADMINSTATE } ); + test.beforeEach( async ( { page } ) => { + await page.goto( 'wp-admin/edit.php?post_type=page' ); + await page.getByRole( 'cell', { name: '“Shop” (Edit)' } ).hover(); + await page + .getByLabel( 'Move “Shop” to the Trash' ) + .click( { force: true } ); + await expect( + page.getByText( 'page moved to the Trash. Undo' ) + ).toBeVisible(); + } ); + + test.afterEach( async ( { page } ) => { + await page.goto( 'wp-admin/edit.php?post_status=trash&post_type=page' ); + await page + .getByRole( 'cell', { name: 'Shop — Shop Page Restore “' } ) + .hover(); + await page + .getByLabel( 'Restore “Shop” from the Trash' ) + .click( { force: true } ); + await expect( + page.getByText( '1 page restored from the Trash.' ) + ).toBeVisible(); + } ); + + test( 'Check the title of the shop page after the page has been deleted', async ( { + page, + } ) => { + await page.goto( '/shop/' ); + expect( await page.title() ).toBe( + 'Shop – WooCommerce Core E2E Test Suite' + ); + } ); +} ); diff --git a/plugins/woocommerce/tests/e2e-pw/utils/cart.js b/plugins/woocommerce/tests/e2e-pw/utils/cart.js new file mode 100644 index 0000000000000..19d05b0486330 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/utils/cart.js @@ -0,0 +1,11 @@ +const addAProductToCart = async ( page, productId ) => { + const responsePromise = page.waitForResponse( + '**/wp-json/wc/store/v1/cart?**' + ); + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await responsePromise; +}; + +module.exports = { + addAProductToCart, +}; diff --git a/plugins/woocommerce/tests/e2e-pw/utils/editor.js b/plugins/woocommerce/tests/e2e-pw/utils/editor.js index 6c221d5a62bed..31516074c8816 100644 --- a/plugins/woocommerce/tests/e2e-pw/utils/editor.js +++ b/plugins/woocommerce/tests/e2e-pw/utils/editor.js @@ -1,3 +1,5 @@ +const { expect } = require( '@playwright/test' ); + const closeWelcomeModal = async ( { page } ) => { // Close welcome popup if prompted try { @@ -24,21 +26,84 @@ const disableWelcomeModal = async ( { page } ) => { } }; +const getCanvas = async ( page ) => { + return page.frame( 'editor-canvas' ) || page; +}; + const goToPageEditor = async ( { page } ) => { await page.goto( 'wp-admin/post-new.php?post_type=page' ); - await disableWelcomeModal( { page } ); }; const goToPostEditor = async ( { page } ) => { await page.goto( 'wp-admin/post-new.php' ); - await disableWelcomeModal( { page } ); }; +const fillPageTitle = async ( page, title ) => { + await ( await getCanvas( page ) ) + .getByRole( 'textbox', { name: 'Add title' } ) + .fill( title ); +}; + +const insertBlock = async ( page, blockName ) => { + const canvas = await getCanvas( page ); + // Click the title to activate the block inserter. + await canvas.getByRole( 'textbox', { name: 'Add title' } ).click(); + await canvas.getByLabel( 'Add block' ).click(); + await page.getByPlaceholder( 'Search', { exact: true } ).fill( blockName ); + await page.getByRole( 'option', { name: blockName, exact: true } ).click(); +}; + +const insertBlockByShortcut = async ( page, blockShortcut ) => { + const canvas = await getCanvas( page ); + await canvas.getByRole( 'button', { name: 'Add default block' } ).click(); + await canvas + .getByRole( 'document', { + name: 'Empty block; start writing or type forward slash to choose a block', + } ) + .fill( blockShortcut ); + await page.keyboard.press( 'Enter' ); +}; + +const transformIntoBlocks = async ( page ) => { + const canvas = await getCanvas( page ); + + await expect( + canvas.locator( + '.wp-block-woocommerce-classic-shortcode__placeholder-copy' + ) + ).toBeVisible(); + await canvas + .getByRole( 'button' ) + .filter( { hasText: 'Transform into blocks' } ) + .click(); + + await expect( page.getByLabel( 'Dismiss this notice' ) ).toContainText( + 'Classic shortcode transformed to blocks.' + ); +}; + +const publishPage = async ( page, pageTitle ) => { + await page.getByRole( 'button', { name: 'Publish', exact: true } ).click(); + await page + .getByRole( 'region', { name: 'Editor publish' } ) + .getByRole( 'button', { name: 'Publish', exact: true } ) + .click(); + await expect( + page.getByText( `${ pageTitle } is now live.` ) + ).toBeVisible(); +}; + module.exports = { closeWelcomeModal, goToPageEditor, goToPostEditor, disableWelcomeModal, + getCanvas, + fillPageTitle, + insertBlock, + insertBlockByShortcut, + transformIntoBlocks, + publishPage, }; diff --git a/plugins/woocommerce/tests/e2e-pw/utils/helpers.js b/plugins/woocommerce/tests/e2e-pw/utils/helpers.js new file mode 100644 index 0000000000000..721aa5064092b --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/utils/helpers.js @@ -0,0 +1,9 @@ +const crypto = require( 'crypto' ); + +const random = ( size = 4 ) => { + return crypto.randomBytes( size ).toString( 'hex' ); +}; + +module.exports = { + random, +}; diff --git a/plugins/woocommerce/tests/e2e-pw/utils/index.js b/plugins/woocommerce/tests/e2e-pw/utils/index.js index 5a44f6ea783d3..ed7823a5a54a7 100644 --- a/plugins/woocommerce/tests/e2e-pw/utils/index.js +++ b/plugins/woocommerce/tests/e2e-pw/utils/index.js @@ -4,6 +4,8 @@ const variableProducts = require( './variable-products' ); const features = require( './features' ); const tours = require( './tours' ); const login = require( './login' ); +const editor = require( './editor' ); +const helpers = require( './helpers' ); module.exports = { api, @@ -12,4 +14,6 @@ module.exports = { features, tours, login, + editor, + helpers, }; diff --git a/plugins/woocommerce/tests/legacy/bootstrap.php b/plugins/woocommerce/tests/legacy/bootstrap.php index 797b7d7d3190c..055d64f5e50d7 100644 --- a/plugins/woocommerce/tests/legacy/bootstrap.php +++ b/plugins/woocommerce/tests/legacy/bootstrap.php @@ -283,6 +283,7 @@ public function includes() { // Traits. require_once $this->tests_dir . '/framework/traits/trait-wc-rest-api-complex-meta.php'; require_once dirname( $this->tests_dir ) . '/php/helpers/HPOSToggleTrait.php'; + require_once dirname( $this->tests_dir ) . '/php/helpers/SerializingCacheTrait.php'; } /** diff --git a/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-customer.php b/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-customer.php index c814ac104d717..78af8c15b5dd3 100644 --- a/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-customer.php +++ b/plugins/woocommerce/tests/legacy/framework/helpers/class-wc-helper-customer.php @@ -101,7 +101,7 @@ public static function get_tax_based_on() { } /** - * Set the the current customer's billing details in the session. + * Set the current customer's billing details in the session. * * @param string $default_shipping_method Shipping Method slug */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/account/functions.php b/plugins/woocommerce/tests/legacy/unit-tests/account/functions.php index b33050637948d..1cb8dae1a2237 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/account/functions.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/account/functions.php @@ -259,6 +259,29 @@ public function test_wc_get_account_saved_payment_methods_list_item_cc() { ); $token->delete( true ); + + // Co-branded credit card. + $token = new WC_Payment_Token_CC(); + $token->set_token( '1001' ); + $token->set_gateway_id( 'bacs' ); + $token->set_card_type( 'cartes_bancaires' ); + $token->set_last4( '1001' ); + $token->set_expiry_month( '12' ); + $token->set_expiry_year( '2020' ); + $token->save(); + + $this->assertEquals( + array( + 'method' => array( + 'last4' => '1001', + 'brand' => 'Cartes Bancaires', + ), + 'expires' => '12/20', + ), + wc_get_account_saved_payment_methods_list_item_cc( array(), $token ) + ); + + $token->delete( true ); } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-integration.php b/plugins/woocommerce/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-integration.php index c2db685751633..c72777baf4036 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-integration.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-integration.php @@ -33,6 +33,18 @@ public function setUp(): void { add_filter( 'woocommerce_maxmind_geolocation_database_service', array( $this, 'override_integration_service' ) ); } + /** + * Clean up after each test. + */ + public function tearDown(): void { + unset( $GLOBALS['wp_filesystem'] ); + + remove_filter( 'filesystem_method', array( $this, 'override_filesystem_method' ) ); + remove_filter( 'woocommerce_maxmind_geolocation_database_service', array( $this, 'override_integration_service' ) ); + + parent::tearDown(); + } + /** * Make sure that the database is not updated if no target database path is given. */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/order-items/class-wc-tests-order-item-product.php b/plugins/woocommerce/tests/legacy/unit-tests/order-items/class-wc-tests-order-item-product.php index 38f6c366e8508..95cd261914079 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/order-items/class-wc-tests-order-item-product.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/order-items/class-wc-tests-order-item-product.php @@ -145,14 +145,14 @@ public function test_get_item_download_url() { $product->save(); $order = new WC_Order(); - $order->set_billing_email( 'test@woo.com' ); + $order->set_billing_email( 'test@woocommerce.com' ); $order->save(); $product_item = new WC_Order_Item_Product(); $product_item->set_product( $product ); $product_item->set_order_id( $order->get_id() ); - $expected_regex = '/download_file=.*&order=wc_order_.*&email=test%40woo.com&key=100/'; + $expected_regex = '/download_file=.*&order=wc_order_.*&email=test%40woocommerce.com&key=100/'; $this->assertMatchesRegularExpression( $expected_regex, $product_item->get_item_download_url( 100 ) ); } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/CustomerHelper.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/CustomerHelper.php index 7b2136d2687fe..ba0f768ee072a 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/CustomerHelper.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/CustomerHelper.php @@ -110,7 +110,7 @@ public static function get_tax_based_on() { } /** - * Set the the current customer's billing details in the session. + * Set the current customer's billing details in the session. * * @param string $default_shipping_method Shipping Method slug */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/system-status.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/system-status.php index a535035038469..a8548813c1356 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/system-status.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version2/system-status.php @@ -201,7 +201,7 @@ public function test_get_system_status_info_settings() { $settings = (array) $this->fetch_or_get_system_status_data_for_user( self::$administrator_user )['settings']; - $this->assertEquals( 17, count( $settings ) ); + $this->assertEquals( 16, count( $settings ) ); $this->assertEquals( ( 'yes' === get_option( 'woocommerce_api_enabled' ) ), $settings['api_enabled'] ); $this->assertEquals( get_woocommerce_currency(), $settings['currency'] ); $this->assertEquals( $term_response, $settings['taxonomies'] ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/orders.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/orders.php index f089c40d7b3db..940662333259b 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/orders.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/orders.php @@ -1143,7 +1143,7 @@ public function test_order_schema() { $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 46, count( $properties ) ); + $this->assertEquals( 47, count( $properties ) ); $this->assertArrayHasKey( 'id', $properties ); } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/reports-orders-totals.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/reports-orders-totals.php index 95e1ec0263210..8c8cf76709aec 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/reports-orders-totals.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/reports-orders-totals.php @@ -6,8 +6,16 @@ * @since 3.5.0 */ +use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper; +use Automattic\WooCommerce\RestApi\UnitTests\HPOSToggleTrait; + +/** + * WC_Tests_API_Reports_Orders_Totals. + */ class WC_Tests_API_Reports_Orders_Totals extends WC_REST_Unit_Test_Case { + use HPOSToggleTrait; + /** * Setup our test server, endpoints, and user info. */ @@ -31,7 +39,7 @@ public function test_register_routes() { } /** - * Test getting all product reviews. + * Test getting order totals. * * @since 3.5.0 */ @@ -60,6 +68,45 @@ public function test_get_reports() { $this->assertEquals( $data, $report ); } + /** + * Test getting order totals with HPOS enabled and some orders pending sync. + * + * @since 8.9.0 + */ + public function test_get_reports_with_hpos_enabled_and_sync_off() { + $this->toggle_cot_authoritative( true ); + $this->disable_cot_sync(); + + wp_set_current_user( $this->user ); + + // Create some orders with HPOS enabled. + $order_counts = array( + 'wc-pending' => 3, + 'wc-processing' => 2, + 'wc-on-hold' => 1, + ); + foreach ( $order_counts as $status => $count ) { + for ( $i = 0; $i < $count; $i++ ) { + $order = OrderHelper::create_order(); + $order->set_status( $status ); + $order->save(); + } + } + + $response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/reports/orders/totals' ) ); + $report = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( count( wc_get_order_statuses() ), count( $report ) ); + foreach ( $report as $data ) { + if ( array_key_exists( 'wc-' . $data['slug'], $order_counts ) ) { + $this->assertEquals( $order_counts[ 'wc-' . $data['slug'] ], $data['total'], 'Status: ' . $data['slug'] ); + } else { + $this->assertEquals( 0, $data['total'], 'Status: ' . $data['slug'] ); + } + } + } + /** * Tests to make sure product reviews cannot be viewed without valid permissions. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/system-status.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/system-status.php index f7e34fad50dfb..54c81fea123b4 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/system-status.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/system-status.php @@ -223,7 +223,7 @@ public function test_get_system_status_info_settings() { $settings = (array) $this->fetch_or_get_system_status_data_for_user( self::$administrator_user )['settings']; - $this->assertEquals( 17, count( $settings ) ); + $this->assertEquals( 16, count( $settings ) ); $this->assertEquals( ( 'yes' === get_option( 'woocommerce_api_enabled' ) ), $settings['api_enabled'] ); $this->assertEquals( get_woocommerce_currency(), $settings['currency'] ); $this->assertEquals( $term_response, $settings['taxonomies'] ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/util/class-wc-tests-core-functions.php b/plugins/woocommerce/tests/legacy/unit-tests/util/class-wc-tests-core-functions.php index c74aae4bc9a21..2d6be76887302 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/util/class-wc-tests-core-functions.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/util/class-wc-tests-core-functions.php @@ -961,8 +961,10 @@ public function test_wc_get_credit_card_type_label() { $this->assertEquals( 'MasterCard', wc_get_credit_card_type_label( 'Mastercard' ) ); $this->assertEquals( 'American Express', wc_get_credit_card_type_label( 'american_express' ) ); $this->assertEquals( 'American Express', wc_get_credit_card_type_label( 'american-express' ) ); + $this->assertEquals( 'Cartes Bancaires', wc_get_credit_card_type_label( 'cartes_bancaires' ) ); + $this->assertEquals( 'Cartes Bancaires', wc_get_credit_card_type_label( 'cartes-bancaires' ) ); $this->assertEquals( '', wc_get_credit_card_type_label( '' ) ); - $this->assertEquals( 'Random name', wc_get_credit_card_type_label( 'random-name' ) ); + $this->assertEquals( 'Random Name', wc_get_credit_card_type_label( 'random-name' ) ); } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes.php index 095f0bdee6a94..32cdd67c2c667 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-taxes.php @@ -18,6 +18,13 @@ class WC_Admin_Tests_API_Reports_Taxes extends WC_REST_Unit_Test_Case { */ protected $endpoint = '/wc-analytics/reports/taxes'; + /** + * Tax option. + * + * @var string + */ + protected $original_tax_option; + /** * Setup test reports taxes data. * @@ -26,6 +33,8 @@ class WC_Admin_Tests_API_Reports_Taxes extends WC_REST_Unit_Test_Case { public function setUp(): void { parent::setUp(); + $this->original_tax_option = get_option( 'woocommerce_calc_taxes' ); + update_option( 'woocommerce_calc_taxes', 'yes' ); $this->user = $this->factory->user->create( array( 'role' => 'administrator', @@ -33,6 +42,15 @@ public function setUp(): void { ); } + /** + * Clean up after each test. DB changes are reverted in parent::tearDown(). + */ + public function tearDown(): void { + parent::tearDown(); + + update_option( 'woocommerce_calc_taxes', $this->original_tax_option ); + } + /** * Test route registration. * @@ -60,20 +78,26 @@ public function test_get_reports() { $product->set_regular_price( 25 ); $product->save(); - $wpdb->insert( + $tax_rate_id = $wpdb->insert( $wpdb->prefix . 'woocommerce_tax_rates', array( 'tax_rate_id' => 1, 'tax_rate' => '7', 'tax_rate_country' => 'US', - 'tax_rate_state' => 'GA', + 'tax_rate_state' => 'NY', 'tax_rate_name' => 'TestTax', 'tax_rate_priority' => 1, 'tax_rate_order' => 1, ) ); + $tax_item = new WC_Order_Item_Tax(); + $tax_item->set_rate( $tax_rate_id ); + $tax_item->set_tax_total( 5 ); + $tax_item->set_shipping_tax_total( 2 ); + $order = WC_Helper_Order::create_order( 1, $product ); + $order->add_item( $tax_item ); $order->set_status( 'completed' ); $order->set_total( 100 ); // $25 x 4. $order->save(); @@ -102,10 +126,9 @@ public function test_get_reports() { $tax_report = reset( $reports ); $this->assertEquals( 1, $tax_report['tax_rate_id'] ); - $this->assertEquals( 'TestTax', $tax_report['name'] ); $this->assertEquals( 7, $tax_report['tax_rate'] ); $this->assertEquals( 'US', $tax_report['country'] ); - $this->assertEquals( 'GA', $tax_report['state'] ); + $this->assertEquals( 'NY', $tax_report['state'] ); $this->assertEquals( 7, $tax_report['total_tax'] ); $this->assertEquals( 5, $tax_report['order_tax'] ); $this->assertEquals( 2, $tax_report['shipping_tax'] ); @@ -134,7 +157,7 @@ public function test_get_reports_taxes_param() { 'tax_rate_id' => 1, 'tax_rate' => '7', 'tax_rate_country' => 'US', - 'tax_rate_state' => 'GA', + 'tax_rate_state' => 'NY', 'tax_rate_name' => 'TestTax', 'tax_rate_priority' => 1, 'tax_rate_order' => 1, @@ -154,11 +177,31 @@ public function test_get_reports_taxes_param() { ) ); + $tax_item = new WC_Order_Item_Tax(); + $tax_item->set_rate( 1 ); + $tax_item->set_tax_total( 5 ); + $tax_item->set_shipping_tax_total( 2 ); + $order = WC_Helper_Order::create_order( 1, $product ); + $order->add_item( $tax_item ); $order->set_status( 'completed' ); $order->set_total( 100 ); // $25 x 4. $order->save(); + $tax_item_ca = new WC_Order_Item_Tax(); + $tax_item_ca->set_rate( 2 ); + $tax_item_ca->set_tax_total( 15 ); + $tax_item_ca->set_shipping_tax_total( 0 ); + + $order_ca = WC_Helper_Order::create_order( 1, $product ); + $order_ca->set_shipping_state( 'ON' ); + $order_ca->set_shipping_country( 'CA' ); + $order_ca->add_item( $tax_item_ca ); + $order_ca->set_status( 'completed' ); + $order_ca->set_total( 100 ); // $25 x 4. + $order_ca->save(); + $order_ca->calculate_totals( true ); + // @todo Remove this once order data is synced to wc_order_tax_lookup $wpdb->insert( $wpdb->prefix . 'wc_order_tax_lookup', @@ -171,6 +214,17 @@ public function test_get_reports_taxes_param() { 'total_tax' => 7, ) ); + $wpdb->insert( + $wpdb->prefix . 'wc_order_tax_lookup', + array( + 'order_id' => $order_ca->get_id(), + 'tax_rate_id' => 2, + 'date_created' => gmdate( 'Y-m-d H:i:s' ), + 'shipping_tax' => 2, + 'order_tax' => 5, + 'total_tax' => 7, + ) + ); WC_Helper_Queue::run_all_pending(); @@ -190,140 +244,26 @@ public function test_get_reports_taxes_param() { $tax_report = reset( $reports ); $this->assertEquals( 2, $tax_report['tax_rate_id'] ); - $this->assertEquals( 'TestTax 2', $tax_report['name'] ); - $this->assertEquals( 8, $tax_report['tax_rate'] ); + $this->assertEquals( 8.0, $tax_report['tax_rate'] ); $this->assertEquals( 'CA', $tax_report['country'] ); $this->assertEquals( 'ON', $tax_report['state'] ); - $this->assertEquals( 0, $tax_report['total_tax'] ); - $this->assertEquals( 0, $tax_report['order_tax'] ); - $this->assertEquals( 0, $tax_report['shipping_tax'] ); - $this->assertEquals( 0, $tax_report['orders_count'] ); + $this->assertEquals( 8.8, $tax_report['total_tax'] ); + $this->assertEquals( 8, $tax_report['order_tax'] ); + $this->assertEquals( 0.8, $tax_report['shipping_tax'] ); + $this->assertEquals( 1, $tax_report['orders_count'] ); $tax_report = next( $reports ); $this->assertEquals( 1, $tax_report['tax_rate_id'] ); - $this->assertEquals( 'TestTax', $tax_report['name'] ); $this->assertEquals( 7, $tax_report['tax_rate'] ); $this->assertEquals( 'US', $tax_report['country'] ); - $this->assertEquals( 'GA', $tax_report['state'] ); + $this->assertEquals( 'NY', $tax_report['state'] ); $this->assertEquals( 7, $tax_report['total_tax'] ); $this->assertEquals( 5, $tax_report['order_tax'] ); $this->assertEquals( 2, $tax_report['shipping_tax'] ); $this->assertEquals( 1, $tax_report['orders_count'] ); } - /** - * Test getting reports with param `orderby=rate`. - * - * @since 3.5.0 - */ - public function test_get_reports_orderby_tax_rate() { - global $wpdb; - wp_set_current_user( $this->user ); - WC_Helper_Reports::reset_stats_dbs(); - - $wpdb->insert( - $wpdb->prefix . 'woocommerce_tax_rates', - array( - 'tax_rate_id' => 1, - 'tax_rate' => '7', - 'tax_rate_country' => 'US', - 'tax_rate_state' => 'GA', - 'tax_rate_name' => 'TestTax', - 'tax_rate_priority' => 1, - 'tax_rate_order' => 1, - ) - ); - - $wpdb->insert( - $wpdb->prefix . 'woocommerce_tax_rates', - array( - 'tax_rate_id' => 2, - 'tax_rate' => '10', - 'tax_rate_country' => 'CA', - 'tax_rate_state' => 'ON', - 'tax_rate_name' => 'TestTax 2', - 'tax_rate_priority' => 1, - 'tax_rate_order' => 1, - ) - ); - - $request = new WP_REST_Request( 'GET', $this->endpoint ); - $request->set_query_params( - array( - 'order' => 'asc', - 'orderby' => 'rate', - 'taxes' => '1,2', - ) - ); - $response = $this->server->dispatch( $request ); - $reports = $response->get_data(); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 2, count( $reports ) ); - - $this->assertEquals( 1, $reports[0]['tax_rate_id'] ); - $this->assertEquals( 7, $reports[0]['tax_rate'] ); - - $this->assertEquals( 2, $reports[1]['tax_rate_id'] ); - $this->assertEquals( 10, $reports[1]['tax_rate'] ); - } - - /** - * Test getting reports with param `orderby=tax_code`. - * - * @since 3.5.0 - */ - public function test_get_reports_orderby_tax_code() { - global $wpdb; - wp_set_current_user( $this->user ); - WC_Helper_Reports::reset_stats_dbs(); - - $wpdb->insert( - $wpdb->prefix . 'woocommerce_tax_rates', - array( - 'tax_rate_id' => 1, - 'tax_rate' => '7', - 'tax_rate_country' => 'US', - 'tax_rate_state' => 'GA', - 'tax_rate_name' => 'TestTax', - 'tax_rate_priority' => 1, - 'tax_rate_order' => 1, - ) - ); - - $wpdb->insert( - $wpdb->prefix . 'woocommerce_tax_rates', - array( - 'tax_rate_id' => 2, - 'tax_rate' => '10', - 'tax_rate_country' => 'CA', - 'tax_rate_state' => 'ON', - 'tax_rate_name' => 'TestTax 2', - 'tax_rate_priority' => 1, - 'tax_rate_order' => 1, - ) - ); - - $request = new WP_REST_Request( 'GET', $this->endpoint ); - $request->set_query_params( - array( - 'order' => 'asc', - 'orderby' => 'tax_code', - 'taxes' => '1,2', - ) - ); - $response = $this->server->dispatch( $request ); - $reports = $response->get_data(); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 2, count( $reports ) ); - - $this->assertEquals( 2, $reports[0]['tax_rate_id'] ); - - $this->assertEquals( 1, $reports[1]['tax_rate_id'] ); - } - /** * Test getting reports without valid permissions. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/includes/class-experimental-abtest-test.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/includes/class-experimental-abtest-test.php index 870e63f86023b..e50ee1d5be1e7 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/includes/class-experimental-abtest-test.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/includes/class-experimental-abtest-test.php @@ -51,7 +51,7 @@ function( $args ) { } /** - * Tests retrieve the test variation when consent is false + * Tests retrieve the default control variation when consent is false */ public function test_get_variation_return_control_when_no_consent() { $exp = new Experimental_Abtest( 'anon', 'platform', false ); @@ -62,7 +62,7 @@ public function test_get_variation_return_control_when_no_consent() { } /** - * Tests retrieve the test variation when consent is false + * Tests retrieve the treatment variation when consent is true and experiment name is valid */ public function test_get_variation() { delete_transient( 'abtest_variation_control' ); @@ -104,15 +104,6 @@ public function test_request_assignment_returns_wp_error_when_anon_id_is_empty() ); } - /** - * Test get_variation with valid experiment name. - */ - public function test_fetch_variation_with_valid_name() { - $exp = new Experimental_Abtest( 'anon', 'platform', true ); - $variation = $exp->get_variation( 'valid_experiment_name_2' ); - $this->assertNotInstanceOf( 'WP_Error', $variation ); - } - /** * Test get_variation with invalid experiment name. */ diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php index 093344d5cca04..165b69cd93cf8 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/reports/class-wc-tests-reports-orders.php @@ -96,7 +96,7 @@ public function test_extended_info() { 'date_created_gmt' => $data->data[0]['date_created_gmt'], // Not under test. 'date' => $data->data[0]['date'], // Not under test. 'extended_info' => array( - 'products' => array( + 'products' => array( array( 'id' => $variation->get_id(), 'name' => $variation->get_name(), @@ -108,8 +108,11 @@ public function test_extended_info() { 'quantity' => 1, ), ), - 'coupons' => array(), - 'customer' => $data->data[0]['extended_info']['customer'], // Not under test. + 'coupons' => array(), + 'customer' => $data->data[0]['extended_info']['customer'], // Not under test. + 'attribution' => array( + 'origin' => 'Unknown', + ), ), ), ), diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php index f132325adbd25..0b326c9821863 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/wc-admin-helper.php @@ -221,7 +221,6 @@ public function test_is_fresh_site_fresh_site_option_must_be_1() { public function get_store_page_test_data() { return array( array( get_permalink( wc_get_page_id( 'cart' ) ), true ), // Test case 1: URL matches cart page. - array( get_permalink( wc_get_page_id( 'myaccount' ) ) . '/orders/', true ), // Test case 2: URL matches my account > orders page. array( 'https://example.com/product-category/sample-category/', true ), // Test case 3: URL matches product category page. array( 'https://example.com/product-tag/sample-tag/', true ), // Test case 4: URL matches product tag page. array( 'https://example.com/shop/uncategorized/test/', true ), // Test case 5: URL matches product page. diff --git a/plugins/woocommerce/tests/metrics/utils.js b/plugins/woocommerce/tests/metrics/utils.js index 48d11ef144a4d..c014ae4b487b0 100644 --- a/plugins/woocommerce/tests/metrics/utils.js +++ b/plugins/woocommerce/tests/metrics/utils.js @@ -1,3 +1,6 @@ +/** + * External dependencies + */ import { existsSync, readFileSync } from 'fs'; export function median( array ) { diff --git a/plugins/woocommerce/tests/performance/bin/init-sample-products.sh b/plugins/woocommerce/tests/performance/bin/init-sample-products.sh index 43642604361be..dfa3db34cf59e 100755 --- a/plugins/woocommerce/tests/performance/bin/init-sample-products.sh +++ b/plugins/woocommerce/tests/performance/bin/init-sample-products.sh @@ -22,6 +22,7 @@ wp-env run tests-cli wp option set woocommerce_product_type 'both' wp-env run tests-cli wp option set woocommerce_allow_tracking 'no' wp-env run tests-cli wp option set woocommerce_enable_checkout_login_reminder 'yes' wp-env run tests-cli wp option set --format=json woocommerce_cod_settings '{"enabled":"yes"}' +wp-env run tests-cli wp option set woocommerce_coming_soon 'no' # WooCommerce shop pages wp-env run tests-cli wp wc --user=admin tool run install_pages diff --git a/plugins/woocommerce/tests/performance/requests/shopper/cart.js b/plugins/woocommerce/tests/performance/requests/shopper/cart.js index 794e750929c4b..21f38e2956b91 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/cart.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/cart.js @@ -72,9 +72,20 @@ export function cart() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: "Cart – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'Cart – WooCommerce Core E2E Test Suite', "body does not contain: 'your cart is currently empty'": ( response ) => ! response.body.includes( 'Your cart is currently empty.' ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/category-page.js b/plugins/woocommerce/tests/performance/requests/shopper/category-page.js index 666299da8eed2..e5be767659bd6 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/category-page.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/category-page.js @@ -44,10 +44,21 @@ export function categoryPage() { ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: "Accessories – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'Accessories – WooCommerce Core E2E Test Suite', "body contains: Category's title": ( response ) => response.body.includes( `

${ product_category }

` ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/checkout-customer-login.js b/plugins/woocommerce/tests/performance/requests/shopper/checkout-customer-login.js index 47a1d03fe68a6..57b4c898c0ec9 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/checkout-customer-login.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/checkout-customer-login.js @@ -70,10 +70,21 @@ export function checkoutCustomerLogin() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: "Checkout – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'Checkout – WooCommerce Core E2E Test Suite', 'body contains checkout class': ( response ) => response.body.includes( 'class="checkout woocommerce-checkout"' ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); // Correlate nonce values for use in subsequent requests. @@ -262,12 +273,23 @@ export function checkoutCustomerLogin() { tags: { name: 'Shopper - Order Received' }, } ); check( response, { + 'title is: "Checkout – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'Checkout – WooCommerce Core E2E Test Suite', "body contains: 'Thank you. Your order has been received.'": ( response ) => response.body.includes( 'Thank you. Your order has been received.' ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); const requestHeadersPost = Object.assign( diff --git a/plugins/woocommerce/tests/performance/requests/shopper/checkout-guest.js b/plugins/woocommerce/tests/performance/requests/shopper/checkout-guest.js index 1a7ff0676aa4d..ef41cce033ca8 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/checkout-guest.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/checkout-guest.js @@ -60,10 +60,21 @@ export function checkoutGuest() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: "Checkout – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'Checkout – WooCommerce Core E2E Test Suite', "body contains: 'woocommerce-checkout' class": ( response ) => response.body.includes( 'class="checkout woocommerce-checkout"' ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); // Correlate nonce values for use in subsequent requests. @@ -173,12 +184,23 @@ export function checkoutGuest() { tags: { name: 'Shopper - Order Received' }, } ); check( response, { + 'title is: "Checkout – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'Checkout – WooCommerce Core E2E Test Suite', "body contains: 'Thank you. Your order has been received.'": ( response ) => response.body.includes( 'Thank you. Your order has been received.' ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); const requestHeadersPost = Object.assign( diff --git a/plugins/woocommerce/tests/performance/requests/shopper/home.js b/plugins/woocommerce/tests/performance/requests/shopper/home.js index 175141375b172..7a7b8ba0498df 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/home.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/home.js @@ -36,6 +36,15 @@ export function homePage() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: "WooCommerce Core E2E Test Suite"': ( response ) => + response.html().find( 'head title' ).text() === + 'WooCommerce Core E2E Test Suite', + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/my-account-orders.js b/plugins/woocommerce/tests/performance/requests/shopper/my-account-orders.js index 3ec046b709e42..20fee9892085b 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/my-account-orders.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/my-account-orders.js @@ -40,10 +40,21 @@ export function myAccountOrders() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: "My account – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'My account – WooCommerce Core E2E Test Suite', 'body contains: my account welcome message': ( response ) => response.body.includes( 'From your account dashboard you can view' ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); } ); @@ -64,8 +75,19 @@ export function myAccountOrders() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: "My account – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'My account – WooCommerce Core E2E Test Suite', "body contains: 'Orders' title": ( response ) => response.body.includes( '>Orders' ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); my_account_order_id = findBetween( response.body, @@ -94,8 +116,19 @@ export function myAccountOrders() { ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: "My account – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'My account – WooCommerce Core E2E Test Suite', "body contains: 'Order number' title": ( response ) => response.body.includes( `${ my_account_order_id }` ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/my-account.js b/plugins/woocommerce/tests/performance/requests/shopper/my-account.js index b3b805eee5ba9..c39626fa5aff0 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/my-account.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/my-account.js @@ -45,8 +45,19 @@ export function myAccount() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: "My account – WooCommerce Core E2E Test Suite"': ( + response + ) => + response.html().find( 'head title' ).text() === + 'My account – WooCommerce Core E2E Test Suite', "body contains: 'My account' title": ( response ) => response.body.includes( '>My account' ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); // Correlate nonce value for use in subsequent requests. diff --git a/plugins/woocommerce/tests/performance/requests/shopper/search-product.js b/plugins/woocommerce/tests/performance/requests/shopper/search-product.js index 4be4b226c23c6..edbdd66226620 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/search-product.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/search-product.js @@ -44,8 +44,25 @@ export function searchProduct() { ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title matches: Search Results for {product_search_term} – WooCommerce Core E2E Test Suite': + ( response ) => { + const title_actual = response + .html() + .find( 'head title' ) + .text(); + const title_expected = new RegExp( + `Search Results for .${ product_search_term }. – WooCommerce Core E2E Test Suite` + ); + return title_actual.match( title_expected ); + }, "body contains: 'Search results' title": ( response ) => response.body.includes( 'Search results:' ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/shop-page.js b/plugins/woocommerce/tests/performance/requests/shopper/shop-page.js index 6a44acb69412b..c1c9e626be840 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/shop-page.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/shop-page.js @@ -36,10 +36,20 @@ export function shopPage() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title equals: Shop – WooCommerce Core E2E Test Suite': ( + response + ) => + response.html().find( 'head title' ).text() === + 'Shop – WooCommerce Core E2E Test Suite', 'body contains: woocommerce-products-header': ( response ) => response.body.includes( '
' ), + 'body contains: woocommerce-loop-product__title': ( response ) => + response + .html() + .find( '.woocommerce-loop-product__title' ) + .toArray().length > 0, } ); } ); diff --git a/plugins/woocommerce/tests/performance/requests/shopper/single-product.js b/plugins/woocommerce/tests/performance/requests/shopper/single-product.js index 62c99023dc957..fa90b42a9beb5 100644 --- a/plugins/woocommerce/tests/performance/requests/shopper/single-product.js +++ b/plugins/woocommerce/tests/performance/requests/shopper/single-product.js @@ -42,8 +42,27 @@ export function singleProduct() { } ); check( response, { 'is status 200': ( r ) => r.status === 200, + 'title is: {product_url} – WooCommerce Core E2E Test Suite': ( + response + ) => { + const title_actual = response + .html() + .find( 'head title' ) + .text(); + const title_expected = new RegExp( + `${ product_url } – WooCommerce Core E2E Test Suite`, + 'i' + ); + return title_actual.match( title_expected ); + }, 'body contains: product SKU': ( response ) => response.body.includes( `class="sku">${ product_sku }` ), + 'footer contains: Built with WooCommerce': ( response ) => + response + .html() + .find( 'body footer' ) + .text() + .includes( 'Built with WooCommerce' ), } ); } ); diff --git a/plugins/woocommerce/tests/php/helpers/SerializingCacheProxy.php b/plugins/woocommerce/tests/php/helpers/SerializingCacheProxy.php new file mode 100644 index 0000000000000..8952073236910 --- /dev/null +++ b/plugins/woocommerce/tests/php/helpers/SerializingCacheProxy.php @@ -0,0 +1,58 @@ +original_cache_instance = $original_cache_instance; + } + + /** + * Proxy method to route all method calls to the underlying cache object. + * + * @param string $func The function name being called. + * @param array $args The arguments to pass to the underlying class. + * + * @return mixed + */ + public function __call( $func, $args ) { + return $this->original_cache_instance->$func( ...$args ); + } + + /** + * Retrieves the cache contents, if it exists. This is a wrapper to the underlying WP_Object_Cache + * instance and will serialize and deserialize any arrays or objects prior to returning in order to + * mimic behaviour of caching where data is stored serialized. + * + * @param int|string $key The key under which the cache contents are stored. + * @param string $group Optional. Where the cache contents are grouped. Default 'default'. + * @param bool $force Optional. Unused. Whether to force an update of the local cache + * from the persistent cache. Default false. + * @param bool $found Optional. Whether the key was found in the cache (passed by reference). + * Disambiguates a return of false, a storable value. Default null. + * + * @return mixed|false The cache contents on success, false on failure to retrieve contents. + */ + public function get( $key, $group = 'default', $force = false, &$found = null ) { + $data = $this->original_cache_instance->get( $key, $group, $found, $found ); + if ( is_object( $data ) || is_array( $data ) ) { + return unserialize( serialize( $data ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize,WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize + } + + return $data; + } +} diff --git a/plugins/woocommerce/tests/php/helpers/SerializingCacheTrait.php b/plugins/woocommerce/tests/php/helpers/SerializingCacheTrait.php new file mode 100644 index 0000000000000..66a792a2d3590 --- /dev/null +++ b/plugins/woocommerce/tests/php/helpers/SerializingCacheTrait.php @@ -0,0 +1,40 @@ +original_cache_instance; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + } + } +} diff --git a/plugins/woocommerce/tests/php/includes/class-wc-order-factory-test.php b/plugins/woocommerce/tests/php/includes/class-wc-order-factory-test.php index 8b7250074fae3..a5d3337a25c77 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-order-factory-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-order-factory-test.php @@ -1,6 +1,7 @@ setup_serializing_cache(); + + $order = OrderHelper::create_order(); + + // Retrieve the order to prime the cache. + $retrieved_order = WC_Order_Factory::get_order( $order->get_id() ); + $this->assertEquals( $order->get_id(), $retrieved_order->get_id() ); + + // Delete the order from the DB to mimic situations like SQL replication lag where the cache has propagated, + // but SQL data hasn't yet caught up. + $wpdb->delete( OrdersTableDataStore::get_orders_table_name(), array( 'ID' => $retrieved_order->get_id() ) ); + foreach ( OrdersTableDataStore::get_all_table_names_with_id() as $table_id => $table ) { + if ( 'orders' !== $table_id ) { + $wpdb->delete( $table, array( 'order_id' => $retrieved_order->get_id() ) ); + } + } + + // Retrieving the order again should either return false because the order was not able to fully load. + $retrieved_order = WC_Order_Factory::get_order( $order->get_id() ); + $this->assertFalse( $retrieved_order ); + } + } diff --git a/plugins/woocommerce/tests/php/includes/class-woocommerce-test.php b/plugins/woocommerce/tests/php/includes/class-woocommerce-test.php index e27603f4b35a3..5f6a79ef6463b 100644 --- a/plugins/woocommerce/tests/php/includes/class-woocommerce-test.php +++ b/plugins/woocommerce/tests/php/includes/class-woocommerce-test.php @@ -27,83 +27,4 @@ public function test_api_property(): void { $this->assertTrue( $property->isPublic() ); $this->assertInstanceOf( WC_API::class, $property->getValue( WC() ) ); } - - /** - * Tear down. - */ - public function tearDown(): void { - parent::tearDown(); - - delete_option( 'woocommerce_coming_soon' ); - delete_option( 'woocommerce_store_pages_only' ); - delete_option( 'woocommerce_private_link' ); - delete_option( 'woocommerce_share_key' ); - delete_option( 'launch-status' ); - } - - /** - * Test for add_lys_default_values method on fresh installation. - */ - public function test_add_lys_default_values_on_fresh_installation() { - update_option( 'fresh_site', '1' ); - - $this->set_current_action( 'woocommerce_newly_installed' ); - ( WooCommerce::instance() )->add_lys_default_values(); - - $this->assertEquals( 'yes', get_option( 'woocommerce_coming_soon' ) ); - $this->assertEquals( 'no', get_option( 'woocommerce_store_pages_only' ) ); - $this->assertEquals( 'yes', get_option( 'woocommerce_private_link' ) ); - $this->assertNotEmpty( get_option( 'woocommerce_share_key' ) ); - $this->assertMatchesRegularExpression( '/^[a-zA-Z0-9]{32}$/', get_option( 'woocommerce_share_key' ) ); - $this->assertEquals( 'unlaunched', get_option( 'launch-status' ) ); - } - - /** - * Test for add_lys_default_values method on WooCommerce update. - */ - public function test_add_lys_default_values_on_woocommerce_update() { - update_option( 'fresh_site', '0' ); - - $this->set_current_action( 'woocommerce_updated' ); - ( WooCommerce::instance() )->add_lys_default_values(); - - $this->assertEquals( 'no', get_option( 'woocommerce_coming_soon' ) ); - $this->assertEquals( 'yes', get_option( 'woocommerce_store_pages_only' ) ); - $this->assertEquals( 'yes', get_option( 'woocommerce_private_link' ) ); - $this->assertNotEmpty( get_option( 'woocommerce_share_key' ) ); - $this->assertMatchesRegularExpression( '/^[a-zA-Z0-9]{32}$/', get_option( 'woocommerce_share_key' ) ); - $this->assertEquals( 'launched', get_option( 'launch-status' ) ); - } - - /** - * Test for add_lys_default_values method when options are already set. - * - */ - public function test_add_lys_default_values_when_options_are_already_set() { - update_option( 'fresh_site', '0' ); - update_option( 'woocommerce_coming_soon', 'yes' ); - update_option( 'woocommerce_store_pages_only', 'no' ); - update_option( 'woocommerce_private_link', 'yes' ); - update_option( 'woocommerce_share_key', 'test' ); - update_option( 'launch-status', 'unlaunched' ); - - $this->set_current_action( 'woocommerce_updated' ); - ( WooCommerce::instance() )->add_lys_default_values(); - - $this->assertEquals( 'yes', get_option( 'woocommerce_coming_soon' ) ); - $this->assertEquals( 'no', get_option( 'woocommerce_store_pages_only' ) ); - $this->assertEquals( 'yes', get_option( 'woocommerce_private_link' ) ); - $this->assertEquals( 'test', get_option( 'woocommerce_share_key' ) ); - $this->assertEquals( 'unlaunched', get_option( 'launch-status' ) ); - } - - /** - * Helper method to set the current action for testing. - * - * @param string $action The action to set. - */ - private function set_current_action( $action ) { - global $wp_current_filter; - $wp_current_filter[] = $action; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - } } diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php index 381f63d832fb2..f7d1f1c01701f 100644 --- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php +++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php @@ -355,4 +355,87 @@ function ( $order_id ) use ( &$order_id_from_after_delete ) { $this->assertSame( $order, $order_from_before_delete ); } } + + /** + * @testDox Deleting order items should only delete items of the specified type. + */ + public function test_delete_items() { + $order = WC_Helper_Order::create_order(); + $product = WC_Helper_Product::create_simple_product(); + $product_item = new WC_Order_Item_Product(); + $product_item->set_product( $product ); + $product_item->set_quantity( 1 ); + $product_item->save(); + + $fee_item_1 = new WC_Order_Item_Fee(); + $fee_item_1->set_amount( 20 ); + $fee_item_1->save(); + + $fee_item_2 = new WC_Order_Item_Fee(); + $fee_item_2->set_amount( 30 ); + $fee_item_2->save(); + + $shipping_item = new WC_Order_Item_Shipping(); + $shipping_item->set_name( 'dummy shipping' ); + $shipping_item->set_total( 20 ); + $shipping_item->save(); + + $order->add_item( $product_item ); + $order->add_item( $fee_item_1 ); + $order->add_item( $fee_item_2 ); + $order->add_item( $shipping_item ); + + $order->save(); + + $r_order = wc_get_order( $order->get_id() ); + $this->assertTrue( $r_order->get_item( $fee_item_1->get_id() )->get_id() === $fee_item_1->get_id() ); + $this->assertTrue( $r_order->get_item( $fee_item_2->get_id() )->get_id() === $fee_item_2->get_id() ); + $this->assertTrue( $r_order->get_item( $product_item->get_id() )->get_id() === $product_item->get_id() ); + $this->assertTrue( $r_order->get_item( $shipping_item->get_id() )->get_id() === $shipping_item->get_id() ); + + // Deleting single item type should only delete that item type. + $r_order->get_data_store()->delete_items( $r_order, $fee_item_1->get_type() ); + $this->assertFalse( $r_order->get_item( $fee_item_1->get_id() ) ); + $this->assertFalse( $r_order->get_item( $fee_item_2->get_id() ) ); + $this->assertTrue( $r_order->get_item( $product_item->get_id() )->get_id() === $product_item->get_id() ); + $this->assertTrue( $r_order->get_item( $shipping_item->get_id() )->get_id() === $shipping_item->get_id() ); + + // Deleting all items should all items. + $r_order->get_data_store()->delete_items( $r_order ); + $this->assertFalse( $r_order->get_item( $fee_item_1->get_id() ) ); + $this->assertFalse( $r_order->get_item( $fee_item_2->get_id() ) ); + $this->assertFalse( $r_order->get_item( $product_item->get_id() ) ); + $this->assertFalse( $r_order->get_item( $shipping_item->get_id() ) ); + } + + /** + * @testDox Deleting order item should delete items from only that order. + */ + public function test_delete_items_multi_order() { + $order_1 = WC_Helper_Order::create_order(); + $product = WC_Helper_Product::create_simple_product(); + $product_item_1 = new WC_Order_Item_Product(); + $product_item_1->set_product( $product ); + $product_item_1->set_quantity( 1 ); + $product_item_1->save(); + + $order_2 = WC_Helper_Order::create_order(); + $product_item_2 = new WC_Order_Item_Product(); + $product_item_2->set_product( $product ); + $product_item_2->set_quantity( 1 ); + $product_item_2->save(); + + $order_1->add_item( $product_item_1 ); + $order_1->save(); + $order_2->add_item( $product_item_2 ); + $order_2->save(); + + $this->assertTrue( $order_1->get_item( $product_item_1->get_id() )->get_id() === $product_item_1->get_id() ); + $this->assertTrue( $order_2->get_item( $product_item_2->get_id() )->get_id() === $product_item_2->get_id() ); + + $order_1->get_data_store()->delete_items( $order_1 ); + + $this->assertFalse( $order_1->get_item( $product_item_1->get_id() ) ); + $this->assertTrue( $order_2->get_item( $product_item_2->get_id() )->get_id() === $product_item_2->get_id() ); + } } diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php index 0ecc362c39cc4..71d588907a5ed 100644 --- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php +++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php @@ -507,4 +507,33 @@ public function test_duplicate_product_with_extra_args() { $this->assertEquals( 'new-sku', $duplicated_product->get_sku() ); $this->assertEquals( 'test', $duplicated_product->get_meta( 'test', true ) ); } + /** + * Test the duplicate product endpoint with to update product's name and stock management. + */ + public function test_duplicate_product_with_extra_args_name_stock_management() { + $product = WC_Helper_Product::create_simple_product( + true, + array( + 'name' => 'Blueberry Cake', + 'sku' => 'blueberry-cake-1', + ) + ); + $product_id = $product->get_id(); + + $request = new WP_REST_Request( 'POST', '/wc/v3/products/' . $product_id . '/duplicate' ); + $request->set_param( 'name', 'new-name' ); + $request->set_param( 'manage_stock', true ); + $request->set_param( 'stock_quantity', 10 ); + $response = $this->server->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + + $response_data = $response->get_data(); + $this->assertArrayHasKey( 'id', $response_data ); + $this->assertNotEquals( $product_id, $response_data['id'] ); + + $duplicated_product = wc_get_product( $response_data['id'] ); + $this->assertEquals( 'new-name (Copy)', $duplicated_product->get_name() ); + $this->assertTrue( $duplicated_product->get_manage_stock() ); + $this->assertEquals( 10, $duplicated_product->get_stock_quantity() ); + } } diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-refunds-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-refunds-controller-tests.php new file mode 100644 index 0000000000000..56b5e3eddf2fc --- /dev/null +++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-refunds-controller-tests.php @@ -0,0 +1,61 @@ +get_items( 'line_item' ) ); + $fee_item = current( $order->get_items( 'fee' ) ); + $shipping_item = current( $order->get_items( 'shipping' ) ); + + $refund = wc_create_refund( + array( + 'order_id' => $order->get_id(), + 'reason' => 'testing', + 'line_items' => array( + $product_item->get_id() => + array( + 'qty' => 1, + 'refund_total' => 1, + ), + $fee_item->get_id() => + array( + 'refund_total' => 10, + ), + $shipping_item->get_id() => + array( + 'refund_total' => 20, + ), + ), + ) + ); + + $refund_ids[] = $refund->get_id(); + ++$orders_and_refunds_count; + } + + $request = new WP_REST_Request( 'GET', '/wc/v3/refunds' ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertIsArray( $data ); + + foreach ( $data as $refund ) { + $this->assertContains( $refund['id'], $refund_ids ); + } + } +} diff --git a/plugins/woocommerce/tests/php/includes/settings/class-wc-settings-advanced-test.php b/plugins/woocommerce/tests/php/includes/settings/class-wc-settings-advanced-test.php index 0983e7e20a445..81233fe1f035c 100644 --- a/plugins/woocommerce/tests/php/includes/settings/class-wc-settings-advanced-test.php +++ b/plugins/woocommerce/tests/php/includes/settings/class-wc-settings-advanced-test.php @@ -108,7 +108,6 @@ public function test_get_default_settings_returns_all_settings( $site_is_https ) 'woocommerce_myaccount_payment_methods_endpoint' => 'text', 'woocommerce_myaccount_lost_password_endpoint' => 'text', 'woocommerce_logout_endpoint' => 'text', - 'woocommerce_coming_soon_page_id' => 'single_select_page_with_search', ); if ( $site_is_https ) { diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/AdditionalFields.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/AdditionalFields.php index d00e0581fce82..82e9642ba1f39 100644 --- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/AdditionalFields.php +++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/AdditionalFields.php @@ -31,6 +31,14 @@ class AdditionalFields extends MockeryTestCase { * @var CheckoutFields */ protected $controller; + + /** + * Products to use in tests. + * + * @var array + */ + protected $products; + /** * Setup products and a cart, as well as register fields. */ @@ -130,11 +138,11 @@ private function register_fields() { array( 'id' => 'plugin-namespace/leave-on-porch', 'label' => __( 'Please leave my package on the porch if I\'m not home', 'woocommerce' ), - 'location' => 'additional', + 'location' => 'order', 'type' => 'checkbox', ), ); - array_map( '__experimental_woocommerce_blocks_register_checkout_field', $this->fields ); + array_map( 'woocommerce_register_additional_checkout_field', $this->fields ); } /** @@ -279,11 +287,11 @@ public function test_checkbox_in_schema() { */ public function test_optional_field_in_schema() { $id = 'plugin-namespace/optional-field'; - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Optional Field', - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', 'required' => false, ) @@ -316,7 +324,7 @@ public function test_missing_id_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', 'A checkout field cannot be registered without an id.', ) )->once(); @@ -330,10 +338,10 @@ public function test_missing_id_in_registration() { 10, 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'label' => 'Invalid ID', - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', 'required' => false, ) @@ -358,7 +366,7 @@ public function test_invalid_id_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( \sprintf( 'Unable to register field with id: "%s". A checkout field id must consist of namespace/name.', $id ) ), ) @@ -373,11 +381,11 @@ public function test_invalid_id_in_registration() { 10, 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Invalid ID', - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', 'required' => false, ) @@ -402,7 +410,7 @@ public function test_missing_label_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( \sprintf( 'Unable to register field with id: "%s". The field label is required.', $id ) ), ) )->once(); @@ -416,10 +424,10 @@ public function test_missing_label_in_registration() { 10, 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', 'required' => false, ) @@ -444,7 +452,7 @@ public function test_missing_location_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( \sprintf( 'Unable to register field with id: "%s". The field location is required.', $id ) ), ) )->once(); @@ -458,7 +466,7 @@ public function test_missing_location_in_registration() { 10, 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Missing Location', @@ -485,7 +493,7 @@ public function test_invalid_location_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( \sprintf( 'Unable to register field with id: "%s". The field location is invalid.', $id ) ), ) )->once(); @@ -500,7 +508,7 @@ public function test_invalid_location_in_registration() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Invalid Location', @@ -530,7 +538,7 @@ public function test_already_registered_field() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( \sprintf( 'Unable to register field with id: "%s". The field is already registered.', $id ) ), ) )->once(); @@ -545,7 +553,7 @@ public function test_already_registered_field() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Government ID', @@ -572,7 +580,7 @@ public function test_invalid_type_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'Unable to register field with id: "%s". Registering a field with type "%s" is not supported. The supported types are: %s.', @@ -594,11 +602,11 @@ public function test_invalid_type_in_registration() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Invalid Type', - 'location' => 'additional', + 'location' => 'order', 'type' => 'invalid', 'required' => false, ) @@ -624,7 +632,7 @@ public function test_invalid_sanitize_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'Unable to register field with id: "%s". %s', $id, 'The sanitize_callback must be a valid callback.' ) ), ) )->once(); @@ -639,11 +647,11 @@ public function test_invalid_sanitize_in_registration() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Invalid Sanitize', - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', 'sanitize_callback' => 'invalid_sanitize_callback', 'required' => false, @@ -670,7 +678,7 @@ public function test_invalid_validate_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'Unable to register field with id: "%s". %s', $id, 'The validate_callback must be a valid callback.' ) ), ) )->once(); @@ -685,11 +693,11 @@ public function test_invalid_validate_in_registration() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Invalid Validate', - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', 'validate_callback' => 'invalid_validate_callback', 'required' => false, @@ -716,7 +724,7 @@ public function test_invalid_attribute_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'An invalid attributes value was supplied when registering field with id: "%s". %s', $id, 'Attributes must be a non-empty array.' ) ), ) )->once(); @@ -731,11 +739,11 @@ public function test_invalid_attribute_in_registration() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Invalid Attribute', - 'location' => 'additional', + 'location' => 'order', 'attributes' => 'invalid', ) ); @@ -786,7 +794,7 @@ public function test_invalid_attributes_values_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'Invalid attribute found when registering field with id: "%s". Attributes: %s are not allowed.', $id, implode( ', ', $invalid_attributes ) ) ), ) )->once(); @@ -801,11 +809,11 @@ public function test_invalid_attributes_values_in_registration() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Invalid Attribute Values', - 'location' => 'additional', + 'location' => 'order', 'attributes' => array( 'title' => 'title', 'maxLength' => '20', @@ -860,7 +868,7 @@ public function test_missing_select_options_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'Unable to register field with id: "%s". %s', $id, 'Fields of type "select" must have an array of "options".' ) ), ) )->once(); @@ -875,11 +883,11 @@ public function test_missing_select_options_in_registration() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Missing Options', - 'location' => 'additional', + 'location' => 'order', 'type' => 'select', 'required' => false, ) @@ -905,7 +913,7 @@ public function test_invalid_select_options_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'Unable to register field with id: "%s". %s', $id, 'Fields of type "select" must have an array of "options" and each option must contain a "value" and "label" member.' ) ), ) )->once(); @@ -920,11 +928,11 @@ public function test_invalid_select_options_in_registration() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Invalid Options', - 'location' => 'additional', + 'location' => 'order', 'type' => 'select', 'options' => array( // numeric array instead of associative array. 'invalidValue', @@ -952,7 +960,7 @@ public function test_duplicate_select_options_in_registration() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'Duplicate key found when registering field with id: "%s". The value in each option of "select" fields must be unique. Duplicate value "%s" found. The duplicate key will be removed.', $id, 'duplicate' ) ), ) )->once(); @@ -967,11 +975,11 @@ public function test_duplicate_select_options_in_registration() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Duplicate Options', - 'location' => 'additional', + 'location' => 'order', 'type' => 'select', 'options' => array( array( @@ -1018,11 +1026,11 @@ public function test_duplicate_select_options_in_registration() { */ public function test_optional_select_has_empty_value() { $id = 'plugin-namespace/optional-select'; - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Optional Select', - 'location' => 'additional', + 'location' => 'order', 'type' => 'select', 'options' => array( array( @@ -1060,7 +1068,7 @@ public function test_invalid_required_prop_checkbox() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'Registering checkbox fields as required is not supported. "%s" will be registered as optional.', $id ) ), ) )->once(); @@ -1075,11 +1083,11 @@ public function test_invalid_required_prop_checkbox() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Checkbox Only Optional', - 'location' => 'additional', + 'location' => 'order', 'type' => 'checkbox', 'required' => true, ) @@ -1119,7 +1127,7 @@ public function test_register_hidden_field_error() { $doing_it_wrong_mocker = \Mockery::mock( 'ActionCallback' ); $doing_it_wrong_mocker->shouldReceive( 'doing_it_wrong_run' )->withArgs( array( - '__experimental_woocommerce_blocks_register_checkout_field', + 'woocommerce_register_additional_checkout_field', \esc_html( sprintf( 'Registering a field with hidden set to true is not supported. The field "%s" will be registered as visible.', $id ) ), ) )->once(); @@ -1134,7 +1142,7 @@ public function test_register_hidden_field_error() { 2 ); - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Hidden Field', @@ -1160,7 +1168,7 @@ public function test_register_hidden_field_error() { ) ); - \__internal_woocommerce_blocks_deregister_checkout_field( $id ); + \__internal_woocommerce_blocks_deregister_checkout_field( $id ); // Ensures the field isn't registered. $this->assertFalse( $this->controller->is_field( $id ), \sprintf( '%s is still registered', $id ) ); @@ -1272,11 +1280,11 @@ public function test_placing_order_with_invalid_text() { */ public function test_placing_order_sanitize_text() { $id = 'plugin-namespace/sanitize-text'; - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Sanitize Text', - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', 'sanitize_callback' => function ( $value ) { return 'sanitized-' . $value; @@ -1338,11 +1346,11 @@ public function test_placing_order_sanitize_text() { */ public function test_placing_order_validate_text() { $id = 'plugin-namespace/validate-text'; - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Validate Text', - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', 'validate_callback' => function ( $value ) { if ( 'invalid' === $value ) { @@ -1407,17 +1415,17 @@ public function test_placing_order_validate_text() { */ public function test_sanitize_filter() { $id = 'plugin-namespace/filter-sanitize'; - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Filter Sanitize', - 'location' => 'additional', + 'location' => 'order', 'type' => 'text', ) ); add_filter( - '__experimental_woocommerce_blocks_sanitize_additional_field', + 'woocommerce_sanitize_additional_field', function ( $value, $key ) use ( $id ) { if ( $key === $id ) { return 'sanitized-' . $value; @@ -1469,6 +1477,7 @@ function ( $value, $key ) use ( $id ) { $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status(), print_r( $data, true ) ); $this->assertEquals( 'sanitized-value', $data['additional_fields'][ $id ], print_r( $data, true ) ); @@ -1483,7 +1492,7 @@ function ( $value, $key ) use ( $id ) { */ public function test_validate_filter() { $id = 'plugin-namespace/filter-validate'; - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'Filter Validate', @@ -1494,7 +1503,7 @@ public function test_validate_filter() { ); add_action( - '__experimental_woocommerce_blocks_validate_additional_field', + 'woocommerce_validate_additional_field', function ( \WP_Error $errors, $key, $value ) use ( $id ) { if ( $key === $id && 'invalid' === $value ) { $errors->add( 'my_invalid_value', 'Invalid value provided.' ); @@ -1560,7 +1569,7 @@ function ( \WP_Error $errors, $key, $value ) use ( $id ) { public function test_place_order_required_address_field() { $id = 'plugin-namespace/my-required-field'; $label = 'My Required Field'; - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => $label, @@ -1624,7 +1633,7 @@ public function test_place_order_required_address_field() { */ public function test_place_order_required_contact_field() { $id = 'plugin-namespace/my-required-contact-field'; - \__experimental_woocommerce_blocks_register_checkout_field( + \woocommerce_register_additional_checkout_field( array( 'id' => $id, 'label' => 'My Required Field', @@ -1732,4 +1741,321 @@ public function test_placing_order_with_invalid_select() { $this->assertEquals( 400, $response->get_status() ); $this->assertEquals( 'plugin-namespace/job-function is not one of director, engineering, customer-support, and other.', $data['data']['params']['additional_fields'], print_r( $data, true ) ); } + + /** + * Ensures that saved values are returned in the cart response. + */ + public function test_previous_values_are_loaded_in_cart() { + $request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' ); + $request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) ); + $request->set_body_params( + array( + 'billing_address' => (object) array( + 'first_name' => 'test', + 'last_name' => 'test', + 'company' => '', + 'address_1' => 'test', + 'address_2' => '', + 'city' => 'test', + 'state' => '', + 'postcode' => 'cb241ab', + 'country' => 'GB', + 'phone' => '', + 'email' => 'testaccount@test.com', + 'plugin-namespace/gov-id' => 'billing-saved-gov-id', + ), + 'shipping_address' => (object) array( + 'first_name' => 'test', + 'last_name' => 'test', + 'company' => '', + 'address_1' => 'test', + 'address_2' => '', + 'city' => 'test', + 'state' => '', + 'postcode' => 'cb241ab', + 'country' => 'GB', + 'phone' => '', + 'plugin-namespace/gov-id' => 'shipping-saved-gov-id', + ), + 'payment_method' => 'bacs', + 'additional_fields' => array( + 'plugin-namespace/job-function' => 'engineering', + 'plugin-namespace/leave-on-porch' => true, + ), + ) + ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status(), print_r( $data, true ) ); + + wc()->cart->add_to_cart( $this->products[0]->get_id(), 2 ); + wc()->cart->add_to_cart( $this->products[1]->get_id(), 1 ); + + $request = new \WP_REST_Request( 'GET', '/wc/store/v1/cart' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status(), print_r( $data, true ) ); + $this->assertEquals( 'billing-saved-gov-id', ( (array) $data['billing_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) ); + $this->assertEquals( 'shipping-saved-gov-id', ( (array) $data['shipping_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) ); + } + + /** + * Ensures that saved values are returned in the checkout response. + */ + public function test_previous_values_are_loaded_in_checkout() { + $request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' ); + $request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) ); + $request->set_body_params( + array( + 'billing_address' => (object) array( + 'first_name' => 'test', + 'last_name' => 'test', + 'company' => '', + 'address_1' => 'test', + 'address_2' => '', + 'city' => 'test', + 'state' => '', + 'postcode' => 'cb241ab', + 'country' => 'GB', + 'phone' => '', + 'email' => 'testaccount@test.com', + 'plugin-namespace/gov-id' => 'billing-saved-gov-id', + ), + 'shipping_address' => (object) array( + 'first_name' => 'test', + 'last_name' => 'test', + 'company' => '', + 'address_1' => 'test', + 'address_2' => '', + 'city' => 'test', + 'state' => '', + 'postcode' => 'cb241ab', + 'country' => 'GB', + 'phone' => '', + 'plugin-namespace/gov-id' => 'shipping-saved-gov-id', + ), + 'payment_method' => 'bacs', + 'additional_fields' => array( + 'plugin-namespace/job-function' => 'engineering', + 'plugin-namespace/leave-on-porch' => true, + ), + ) + ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status(), print_r( $data, true ) ); + + wc()->cart->add_to_cart( $this->products[0]->get_id(), 2 ); + wc()->cart->add_to_cart( $this->products[1]->get_id(), 1 ); + + $request = new \WP_REST_Request( 'GET', '/wc/store/v1/checkout' ); + $request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status(), print_r( $data, true ) ); + $this->assertEquals( 'billing-saved-gov-id', ( (array) $data['billing_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) ); + $this->assertEquals( 'shipping-saved-gov-id', ( (array) $data['shipping_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) ); + $this->assertEquals( 'engineering', ( (array) $data['additional_fields'] )['plugin-namespace/job-function'], print_r( $data, true ) ); + $this->assertArrayNotHasKey( 'plugin-namespace/leave-on-porch', $data['additional_fields'], print_r( $data, true ) ); + } + + /** + * Ensures we can provide a default value if a field is not set. + */ + public function test_reading_values_externally() { + $id = 'plugin-namespace/my-external-field'; + \woocommerce_register_additional_checkout_field( + array( + 'id' => $id, + 'label' => 'My Required Field', + 'location' => 'order', + 'type' => 'text', + 'required' => true, + ) + ); + + add_filter( + "woocommerce_get_default_value_for_{$id}", + function () { + return 'external-value'; + }, + 10, + 2 + ); + + $request = new \WP_REST_Request( 'GET', '/wc/store/v1/checkout' ); + $request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status(), print_r( $data, true ) ); + $this->assertEquals( 'external-value', ( (array) $data['additional_fields'] )[ $id ], print_r( $data, true ) ); + + \__internal_woocommerce_blocks_deregister_checkout_field( $id ); + + \remove_all_filters( "woocommerce_get_default_value_for_{$id}" ); + } + + /** + * Ensures that we cannot overwrite existing fields. + */ + public function test_not_overwriting_values() { + $id = 'plugin-namespace/my-external-field'; + \woocommerce_register_additional_checkout_field( + array( + 'id' => $id, + 'label' => 'My Required Field', + 'location' => 'order', + 'type' => 'text', + 'required' => true, + ) + ); + + add_filter( + "woocommerce_get_default_value_for_{$id}", + function () { + return 'external-value'; + }, + 10, + 2 + ); + + $request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' ); + $request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) ); + $request->set_body_params( + array( + 'billing_address' => (object) array( + 'first_name' => 'test', + 'last_name' => 'test', + 'company' => '', + 'address_1' => 'test', + 'address_2' => '', + 'city' => 'test', + 'state' => '', + 'postcode' => 'cb241ab', + 'country' => 'GB', + 'phone' => '', + 'email' => 'testaccount@test.com', + 'plugin-namespace/gov-id' => 'gov id', + ), + 'shipping_address' => (object) array( + 'first_name' => 'test', + 'last_name' => 'test', + 'company' => '', + 'address_1' => 'test', + 'address_2' => '', + 'city' => 'test', + 'state' => '', + 'postcode' => 'cb241ab', + 'country' => 'GB', + 'phone' => '', + 'plugin-namespace/gov-id' => 'gov id', + ), + 'payment_method' => 'bacs', + 'additional_fields' => array( + 'plugin-namespace/job-function' => 'engineering', + $id => 'value', + ), + ) + ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status(), print_r( $data, true ) ); + $this->assertEquals( 'value', ( (array) $data['additional_fields'] )[ $id ], print_r( $data, true ) ); + + \__internal_woocommerce_blocks_deregister_checkout_field( $id ); + \remove_all_filters( "woocommerce_get_default_value_for_{$id}" ); + } + + /** + * Ensures that we can react to saving a field. + */ + public function test_reacting_to_value_save() { + $this->unregister_fields(); + $id = 'plugin-namespace/my-external-field'; + \woocommerce_register_additional_checkout_field( + array( + 'id' => $id, + 'label' => 'My Required Field', + 'location' => 'order', + 'type' => 'text', + 'required' => true, + ) + ); + + $set_value_mocker = \Mockery::mock( 'ActionCallback' ); + $set_value_mocker->shouldReceive( 'woocommerce_set_additional_field_value' )->withArgs( + array( + $id, + 'my-value', + 'other', + \Mockery::type( 'WC_Order' ), + ) + )->atLeast( 1 ); + + add_action( + 'woocommerce_set_additional_field_value', + array( + $set_value_mocker, + 'woocommerce_set_additional_field_value', + ), + 10, + 4 + ); + + $request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' ); + $request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) ); + $request->set_body_params( + array( + 'billing_address' => (object) array( + 'first_name' => 'test', + 'last_name' => 'test', + 'company' => '', + 'address_1' => 'test', + 'address_2' => '', + 'city' => 'test', + 'state' => '', + 'postcode' => 'cb241ab', + 'country' => 'GB', + 'phone' => '', + 'email' => 'testaccount@test.com', + ), + 'shipping_address' => (object) array( + 'first_name' => 'test', + 'last_name' => 'test', + 'company' => '', + 'address_1' => 'test', + 'address_2' => '', + 'city' => 'test', + 'state' => '', + 'postcode' => 'cb241ab', + 'country' => 'GB', + 'phone' => '', + ), + 'payment_method' => 'bacs', + 'additional_fields' => array( + $id => 'my-value', + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status(), print_r( $data, true ) ); + + \remove_action( + 'woocommerce_set_additional_field_value', + array( + $set_value_mocker, + 'woocommerce_set_additional_field_value', + ) + ); + } } diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Logging/SettingsTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Logging/SettingsTest.php index accff71d16525..3b80d47740239 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Admin/Logging/SettingsTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Logging/SettingsTest.php @@ -117,8 +117,6 @@ public function test_get_log_directory_creation() { $upload_dir = wp_upload_dir(); $path = $upload_dir['basedir'] . '/wc-logs-test/'; - $this->assertFalse( wp_is_writable( $path ) ); - $callback = fn() => $path; add_filter( 'woocommerce_log_directory', $callback ); diff --git a/plugins/woocommerce/tests/php/src/Internal/ComingSoon/ComingSoonCacheInvalidatorTest.php b/plugins/woocommerce/tests/php/src/Internal/ComingSoon/ComingSoonCacheInvalidatorTest.php new file mode 100644 index 0000000000000..1e9985a32f2e2 --- /dev/null +++ b/plugins/woocommerce/tests/php/src/Internal/ComingSoon/ComingSoonCacheInvalidatorTest.php @@ -0,0 +1,70 @@ +sut = wc_get_container()->get( ComingSoonCacheInvalidator::class ); + } + + /** + * @testdox Test cache invalidation when coming soon option is changed to yes. + */ + public function test_cache_invalidated_when_coming_soon_option_is_changed_yes() { + update_option( 'woocommerce_coming_soon', 'no' ); + wp_cache_set( 'test_foo', 'bar' ); + update_option( 'woocommerce_coming_soon', 'yes' ); + + $this->assertFalse( wp_cache_get( 'test_foo' ) ); + } + + /** + * @testdox Test cache invalidation when coming soon option is changed to no. + */ + public function test_cache_invalidated_when_coming_soon_option_is_changed_no() { + update_option( 'woocommerce_coming_soon', 'yes' ); + wp_cache_set( 'test_foo', 'bar' ); + update_option( 'woocommerce_coming_soon', 'no' ); + + $this->assertFalse( wp_cache_get( 'test_foo' ) ); + } + + /** + * @testdox Test cache invalidation when store pages only option is changed to yes. + */ + public function test_cache_invalidated_when_store_pages_only_option_is_changed_yes() { + update_option( 'woocommerce_store_pages_only', 'no' ); + wp_cache_set( 'test_foo', 'bar' ); + update_option( 'woocommerce_store_pages_only', 'yes' ); + + $this->assertFalse( wp_cache_get( 'test_foo' ) ); + } + + /** + * @testdox Test cache invalidation when store pages only option is changed to no. + */ + public function test_cache_invalidated_when_store_pages_only_option_is_changed_no() { + update_option( 'woocommerce_store_pages_only', 'yes' ); + wp_cache_set( 'test_foo', 'bar' ); + update_option( 'woocommerce_store_pages_only', 'no' ); + + $this->assertFalse( wp_cache_get( 'test_foo' ) ); + } +} diff --git a/plugins/woocommerce/tests/php/src/Internal/ComingSoon/ComingSoonHelperTest.php b/plugins/woocommerce/tests/php/src/Internal/ComingSoon/ComingSoonHelperTest.php new file mode 100644 index 0000000000000..72928cbc6d1b1 --- /dev/null +++ b/plugins/woocommerce/tests/php/src/Internal/ComingSoon/ComingSoonHelperTest.php @@ -0,0 +1,119 @@ +sut = wc_get_container()->get( ComingSoonHelper::class ); + } + + /** + * @testdox Test is_site_live() behavior when coming soon option is no. + */ + public function test_is_site_live_when_coming_soon_is_no() { + update_option( 'woocommerce_coming_soon', 'no' ); + $this->assertTrue( $this->sut->is_site_live() ); + } + + /** + * @testdox Test is_site_live() behavior when coming soon option is yes. + */ + public function test_is_site_live_when_coming_soon_is_yes() { + update_option( 'woocommerce_coming_soon', 'yes' ); + $this->assertFalse( $this->sut->is_site_live() ); + } + + /** + * @testdox Test is_site_live() behavior when coming soon option is not available. + */ + public function test_is_site_live_when_coming_soon_is_na() { + delete_option( 'woocommerce_coming_soon' ); + $this->assertTrue( $this->sut->is_site_live() ); + } + + /** + * @testdox Test is_site_coming_soon() behavior when coming soon option is no. + */ + public function test_is_site_coming_soon_when_coming_soon_is_no() { + update_option( 'woocommerce_coming_soon', 'no' ); + $this->assertFalse( $this->sut->is_site_coming_soon() ); + } + + /** + * @testdox Test is_site_coming_soon() behavior when coming soon option is not available. + */ + public function test_is_site_coming_soon_when_coming_soon_is_na() { + delete_option( 'woocommerce_coming_soon', 'no' ); + $this->assertFalse( $this->sut->is_site_coming_soon() ); + } + + /** + * @testdox Test is_site_coming_soon() behavior when store pages only option is no. + */ + public function test_is_site_coming_soon_when_store_pages_only_is_no() { + update_option( 'woocommerce_coming_soon', 'yes' ); + update_option( 'woocommerce_store_pages_only', 'no' ); + $this->assertTrue( $this->sut->is_site_coming_soon() ); + } + + /** + * @testdox Test is_site_coming_soon() behavior when store pages only option is yes. + */ + public function test_is_site_coming_soon_when_store_pages_only_is_yes() { + update_option( 'woocommerce_coming_soon', 'yes' ); + update_option( 'woocommerce_store_pages_only', 'yes' ); + $this->assertFalse( $this->sut->is_site_coming_soon() ); + } + + /** + * @testdox Test is_store_coming_soon() behavior when coming soon option is no. + */ + public function test_is_srote_coming_soon_when_coming_soon_is_no() { + update_option( 'woocommerce_coming_soon', 'no' ); + $this->assertFalse( $this->sut->is_site_coming_soon() ); + } + + /** + * @testdox Test is_store_coming_soon() behavior when coming soon option is not available. + */ + public function test_is_store_coming_soon_when_coming_soon_is_na() { + delete_option( 'woocommerce_coming_soon', 'no' ); + $this->assertFalse( $this->sut->is_store_coming_soon() ); + } + + /** + * @testdox Test is_store_coming_soon() behavior when store pages only option is no. + */ + public function test_is_store_coming_soon_when_store_pages_only_is_no() { + update_option( 'woocommerce_coming_soon', 'yes' ); + update_option( 'woocommerce_store_pages_only', 'no' ); + $this->assertFalse( $this->sut->is_store_coming_soon() ); + } + + /** + * @testdox Test is_store_coming_soon() behavior when store pages only option is yes. + */ + public function test_is_store_coming_soon_when_store_pages_only_is_yes() { + update_option( 'woocommerce_coming_soon', 'yes' ); + update_option( 'woocommerce_store_pages_only', 'yes' ); + $this->assertTrue( $this->sut->is_store_coming_soon() ); + } +} diff --git a/plugins/woocommerce/tests/php/src/Internal/ComingSoon/ComingSoonRequestHandlerTest.php b/plugins/woocommerce/tests/php/src/Internal/ComingSoon/ComingSoonRequestHandlerTest.php new file mode 100644 index 0000000000000..0b6c9f3ba11b3 --- /dev/null +++ b/plugins/woocommerce/tests/php/src/Internal/ComingSoon/ComingSoonRequestHandlerTest.php @@ -0,0 +1,73 @@ +sut = wc_get_container()->get( ComingSoonRequestHandler::class ); + } + + /** + * @testdox Test request parser displays a coming soon page to public visitor. + */ + public function test_coming_soon_mode_shown_to_visitor() { + $this->markTestSkipped( 'The die statement breaks the test. To be improved.' ); + update_option( 'woocommerce_coming_soon', 'yes' ); + $wp = new \WP(); + $wp->request = '/'; + do_action_ref_array( 'parse_request', array( &$wp ) ); + + $this->assertSame( $wp->query_vars['page_id'], 99 ); + } + + /** + * @testdox Test request parser displays a live page to public visitor. + */ + public function test_live_mode_shown_to_visitor() { + $this->markTestSkipped( 'The die statement breaks the test. To be improved.' ); + update_option( 'woocommerce_coming_soon', 'no' ); + $wp = new \WP(); + $wp->request = '/'; + do_action_ref_array( 'parse_request', array( &$wp ) ); + + $this->assertArrayNotHasKey( 'page_id', $wp->query_vars ); + } + + /** + * @testdox Test request parser excludes admins. + */ + public function test_shop_manager_exclusion() { + $this->markTestSkipped( 'Failing in CI but not locally. To be investigated.' ); + update_option( 'woocommerce_coming_soon', 'yes' ); + $user_id = $this->factory->user->create( + array( + 'role' => 'shop_manager', + ) + ); + wp_set_current_user( $user_id ); + + $wp = new \WP(); + $wp->request = '/'; + do_action_ref_array( 'parse_request', array( &$wp ) ); + + $this->assertSame( $wp->query_vars['page_id'], null ); + } +} diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableQueryTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableQueryTests.php index 21ff35190f58b..acddc1c05fb21 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableQueryTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableQueryTests.php @@ -1,5 +1,6 @@ setup_cot(); $this->cot_state = OrderUtil::custom_orders_table_usage_is_enabled(); + $this->setup_cot(); $this->toggle_cot_feature_and_usage( true ); } @@ -148,7 +149,7 @@ public function test_query_suppress_filters() { $filters_called = 0; $filter_callback = function ( $arg ) use ( &$filters_called ) { - $filters_called++; + ++$filters_called; return $arg; }; @@ -193,7 +194,7 @@ public function test_query_filters() { $this->assertCount( 2, wc_get_orders( array() ) ); // Force a query that returns nothing. - $filter_callback = function( $clauses ) { + $filter_callback = function ( $clauses ) { $clauses['where'] .= ' AND 1=0 '; return $clauses; }; @@ -203,7 +204,7 @@ public function test_query_filters() { remove_all_filters( 'woocommerce_orders_table_query_clauses' ); // Force a query that sorts orders by id ASC (as opposed to the default date DESC) if a query arg is present. - $filter_callback = function( $clauses, $query, $query_args ) { + $filter_callback = function ( $clauses, $query, $query_args ) { if ( ! empty( $query_args['my_custom_arg'] ) ) { $clauses['orderby'] = $query->get_table_name( 'orders' ) . '.id ASC'; } @@ -254,7 +255,7 @@ public function test_pre_query_escape_hook_simple() { $this->assertEquals( 2, $query->found_orders ); $this->assertEquals( 0, $query->max_num_pages ); - $callback = function( $result, $query_object, $sql ) use ( $order1 ) { + $callback = function ( $result, $query_object, $sql ) use ( $order1 ) { $this->assertNull( $result ); $this->assertInstanceOf( OrdersTableQuery::class, $query_object ); $this->assertStringContainsString( 'SELECT ', $sql ); @@ -295,7 +296,7 @@ public function test_pre_query_escape_hook_with_pagination() { $this->assertEquals( 2, $query->found_orders ); $this->assertEquals( 0, $query->max_num_pages ); - $callback = function( $result, $query_object, $sql ) use ( $order1 ) { + $callback = function ( $result, $query_object, $sql ) use ( $order1 ) { $this->assertNull( $result ); $this->assertInstanceOf( OrdersTableQuery::class, $query_object ); $this->assertStringContainsString( 'SELECT ', $sql ); @@ -330,7 +331,7 @@ public function test_pre_query_escape_hook_pass_limit() { $order1->set_date_created( time() - HOUR_IN_SECONDS ); $order1->save(); - $callback = function( $result, $query_object, $sql ) use ( $order1 ) { + $callback = function () use ( $order1 ) { // Do not return found_orders or max_num_pages so as to provoke a warning. $order_ids = array( $order1->get_id() ); return array( $order_ids, 10, null ); @@ -385,7 +386,7 @@ public function test_pre_query_escape_hook_return_null_limit() { $order1->set_date_created( time() - HOUR_IN_SECONDS ); $order1->save(); - $callback = function( $result, $query_object, $sql ) use ( $order1 ) { + $callback = function () use ( $order1 ) { // Just return null. return null; }; diff --git a/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php b/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php index 56667d49bb85f..a301705389ab7 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Features/FeaturesControllerTest.php @@ -402,7 +402,7 @@ public function test_declare_compatibility_and_incompatibility_for_the_same_plug $this->simulate_inside_before_woocommerce_init_hook(); $this->ExpectException( \Exception::class ); - $this->ExpectExceptionMessage( "Plugin the_plugin is trying to declare itself as incompatible with the 'mature1' feature, but it already declared itself as compatible" ); + $this->ExpectExceptionMessage( esc_html( "Plugin the_plugin is trying to declare itself as incompatible with the 'mature1' feature, but it already declared itself as compatible" ) ); $this->sut->declare_compatibility( 'mature1', 'the_plugin', true ); $this->sut->declare_compatibility( 'mature1', 'the_plugin', false ); diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/DatabaseUtilTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/DatabaseUtilTest.php index a00c57dc62773..cb0572a576083 100644 --- a/plugins/woocommerce/tests/php/src/Internal/Utilities/DatabaseUtilTest.php +++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/DatabaseUtilTest.php @@ -3,6 +3,7 @@ * Tests for the DatabaseUtil utility. */ +use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer; use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil; /** @@ -57,4 +58,32 @@ public function test_get_index_columns_returns_empty_for_invalid_index() { $this->sut->get_index_columns( $wpdb->prefix . 'wc_product_meta_lookup', 'invalid_index_name' ) ); } + + /** + * @test Test that we are able to create FTS index on order address table. + */ + public function test_create_fts_index_order_address_table() { + $db = wc_get_container()->get( DataSynchronizer::class ); + // Remove the Test Suite’s use of temporary tables https://wordpress.stackexchange.com/a/220308. + remove_filter( 'query', array( $this, '_create_temporary_tables' ) ); + remove_filter( 'query', array( $this, '_drop_temporary_tables' ) ); + $db->create_database_tables(); + // Add back removed filter. + add_filter( 'query', array( $this, '_create_temporary_tables' ) ); + add_filter( 'query', array( $this, '_drop_temporary_tables' ) ); + if ( ! $this->sut->fts_index_on_order_item_table_exists() ) { + $this->sut->create_fts_index_order_address_table(); + } + $this->assertTrue( $this->sut->fts_index_on_order_address_table_exists() ); + } + + /** + * @test Test that we are able to create FTS index on order item table. + */ + public function test_create_fts_index_order_item_table() { + if ( ! $this->sut->fts_index_on_order_item_table_exists() ) { + $this->sut->create_fts_index_order_item_table(); + } + $this->assertTrue( $this->sut->fts_index_on_order_item_table_exists() ); + } } diff --git a/plugins/woocommerce/tests/php/src/Internal/Utilities/FilesystemUtilTest.php b/plugins/woocommerce/tests/php/src/Internal/Utilities/FilesystemUtilTest.php new file mode 100644 index 0000000000000..f56baac1fc3cc --- /dev/null +++ b/plugins/woocommerce/tests/php/src/Internal/Utilities/FilesystemUtilTest.php @@ -0,0 +1,61 @@ + 'direct'; + add_filter( 'filesystem_method', $callback ); + + $this->assertInstanceOf( WP_Filesystem_Base::class, FilesystemUtil::get_wp_filesystem() ); + + remove_filter( 'filesystem_method', $callback ); + } + + /** + * @testdox Check that the get_wp_filesystem method throws an exception when the filesystem cannot be initialized. + */ + public function test_get_wp_filesystem_failure(): void { + $this->expectException( 'Exception' ); + + $callback = fn() => 'asdf'; + add_filter( 'filesystem_method', $callback ); + + FilesystemUtil::get_wp_filesystem(); + + remove_filter( 'filesystem_method', $callback ); + } +} diff --git a/plugins/woocommerce/woocommerce.php b/plugins/woocommerce/woocommerce.php index a025d52599b5f..52fe54f0e1d29 100644 --- a/plugins/woocommerce/woocommerce.php +++ b/plugins/woocommerce/woocommerce.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce * Plugin URI: https://woocommerce.com/ * Description: An ecommerce toolkit that helps you sell anything. Beautifully. - * Version: 8.9.0-dev + * Version: 9.0.0-dev * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woocommerce diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b13ba139463e..a8ea372c03d48 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,8 +69,8 @@ importers: specifier: ^6.8.1 version: 6.8.1(webpack@5.89.0) glob: - specifier: ^7.2.3 - version: 7.2.3 + specifier: ^10.3.10 + version: 10.3.10 husky: specifier: ^9.0.11 version: 9.0.11 @@ -102,8 +102,8 @@ importers: specifier: ^2.88.2 version: 2.88.2 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 sass: specifier: ^1.69.5 version: 1.69.5 @@ -121,7 +121,7 @@ importers: version: 1.1.2(webpack@5.89.0) webpack: specifier: ^5.89.0 - version: 5.89.0(webpack-cli@3.3.12) + version: 5.89.0(webpack-cli@4.10.0) packages/js/admin-e2e-tests: dependencies: @@ -178,8 +178,8 @@ importers: specifier: ^1.0.18 version: 1.0.18(jest@27.5.1)(typescript@5.3.3) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -242,8 +242,8 @@ importers: specifier: ^17.0.2 version: 17.0.2(react@17.0.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 sass-loader: specifier: ^10.5.0 version: 10.5.0(sass@1.69.5)(webpack@5.89.0) @@ -381,8 +381,8 @@ importers: specifier: link:@testing-library/react-hooks^8.0.1 version: link:@testing-library/react-hooks^8.0.1 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 sass-loader: specifier: ^10.5.0 version: 10.5.0(sass@1.69.5)(webpack@5.89.0) @@ -576,8 +576,8 @@ importers: specifier: ^17.0.2 version: 17.0.2(react@17.0.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 sass-loader: specifier: ^10.5.0 version: 10.5.0(sass@1.69.5)(webpack@5.89.0) @@ -907,8 +907,8 @@ importers: specifier: ^17.0.2 version: 17.0.2 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 sass-loader: specifier: ^10.5.0 version: 10.5.0(sass@1.69.5)(webpack@5.89.0) @@ -919,8 +919,8 @@ importers: specifier: ^5.3.3 version: 5.3.3 uuid: - specifier: ^8.3.2 - version: 8.3.2 + specifier: ^9.0.1 + version: 9.0.1 webpack: specifier: ^5.89.0 version: 5.89.0(webpack-cli@3.3.12) @@ -977,8 +977,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -1035,8 +1035,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -1150,8 +1150,8 @@ importers: specifier: ^4.3.0 version: 4.3.0(postcss@8.4.32)(webpack@5.89.0) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 sass-loader: specifier: ^10.5.0 version: 10.5.0(sass@1.69.5)(webpack@5.89.0) @@ -1298,8 +1298,8 @@ importers: specifier: ^4.2.1 version: 4.2.1 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -1371,8 +1371,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -1405,8 +1405,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -1647,6 +1647,9 @@ importers: '@typescript-eslint/parser': specifier: ^5.62.0 version: 5.62.0(eslint@8.55.0)(typescript@5.3.3) + '@woocommerce/e2e-environment': + specifier: workspace:* + version: link:../e2e-environment '@woocommerce/eslint-plugin': specifier: workspace:* version: link:../eslint-plugin @@ -1706,8 +1709,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -1836,8 +1839,8 @@ importers: specifier: ^4.3.0 version: 4.3.0(postcss@8.4.32)(webpack@5.89.0) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 sass-loader: specifier: ^10.5.0 version: 10.5.0(sass@1.69.5)(webpack@5.89.0) @@ -1915,8 +1918,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -1961,8 +1964,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -2006,8 +2009,8 @@ importers: specifier: ^8.1.5 version: 8.1.5 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 write-pkg: specifier: ^4.0.0 version: 4.0.0 @@ -2088,8 +2091,8 @@ importers: specifier: ^8.55.0 version: 8.55.0 glob: - specifier: ^7.2.3 - version: 7.2.3 + specifier: ^10.3.10 + version: 10.3.10 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -2143,8 +2146,8 @@ importers: specifier: 1.5.1 version: 1.5.1 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -2204,8 +2207,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -2214,7 +2217,7 @@ importers: version: 5.3.3 webpack: specifier: ^5.89.0 - version: 5.89.0(webpack-cli@3.3.12) + version: 5.89.0(webpack-cli@4.10.0) wireit: specifier: 0.14.3 version: 0.14.3 @@ -2289,8 +2292,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -2356,8 +2359,8 @@ importers: specifier: ^4.2.1 version: 4.2.1 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -2405,8 +2408,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -2511,8 +2514,8 @@ importers: specifier: ^4.3.0 version: 4.3.0(postcss@8.4.32)(webpack@5.89.0) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 sass-loader: specifier: ^10.5.0 version: 10.5.0(sass@1.69.5)(webpack@5.89.0) @@ -2779,8 +2782,8 @@ importers: specifier: ^17.0.2 version: 17.0.2(react@17.0.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 sass-loader: specifier: ^10.5.0 version: 10.5.0(sass@1.69.5)(webpack@5.89.0) @@ -2834,8 +2837,8 @@ importers: specifier: ~27.5.1 version: 27.5.1(ts-node@10.9.2) rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 ts-jest: specifier: ~29.1.1 version: 29.1.1(@babel/core@7.23.5)(babel-jest@27.5.1)(jest@27.5.1)(typescript@5.3.3) @@ -2992,7 +2995,7 @@ importers: version: /wp-prettier@2.8.5 ts-loader: specifier: ^9.5.1 - version: 9.5.1(typescript@5.3.3)(webpack@5.89.0) + version: 9.5.1(typescript@5.3.3)(webpack@5.91.0) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -3145,8 +3148,8 @@ importers: specifier: ^5.3.3 version: 5.3.3 uuid: - specifier: ^8.3.2 - version: 8.3.2 + specifier: ^9.0.1 + version: 9.0.1 webpack: specifier: 5.70.0 version: 5.70.0(webpack-cli@3.3.12) @@ -3692,8 +3695,8 @@ importers: specifier: ^1.2.2 version: 1.2.2 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: 5.0.5 + version: 5.0.5 rtlcss: specifier: ^2.6.2 version: 2.6.2 @@ -3841,7 +3844,7 @@ importers: version: /wp-prettier@2.8.5 ts-loader: specifier: ^9.5.1 - version: 9.5.1(typescript@5.3.3)(webpack@5.89.0) + version: 9.5.1(typescript@5.3.3)(webpack@5.91.0) typescript: specifier: ^5.3.3 version: 5.3.3 @@ -3854,6 +3857,9 @@ importers: plugins/woocommerce-blocks: dependencies: + '@ariakit/react': + specifier: ^0.4.4 + version: 0.4.5(react-dom@17.0.2)(react@17.0.2) '@dnd-kit/core': specifier: ^6.1.0 version: 6.1.0(react-dom@17.0.2)(react@17.0.2) @@ -3921,8 +3927,8 @@ importers: specifier: 3.3.7 version: 3.3.7 dataloader: - specifier: 2.1.0 - version: 2.1.0 + specifier: 2.2.2 + version: 2.2.2 deepsignal: specifier: 1.3.6 version: 1.3.6(@preact/signals@1.2.2)(preact@10.19.3) @@ -4050,7 +4056,7 @@ importers: version: 7.5.2(@types/react-dom@18.0.10)(@types/react@17.0.71)(react-dom@17.0.2)(react@17.0.2) '@storybook/addon-styling-webpack': specifier: ^0.0.5 - version: 0.0.5(webpack@5.88.2) + version: 0.0.5(webpack@5.91.0) '@storybook/addons': specifier: 7.5.2 version: 7.5.2(react-dom@17.0.2)(react@17.0.2) @@ -4203,7 +4209,7 @@ importers: version: 4.44.0 '@wordpress/dependency-extraction-webpack-plugin': specifier: 4.28.0 - version: 4.28.0(webpack@5.88.2) + version: 4.28.0(webpack@5.91.0) '@wordpress/dom': specifier: 3.27.0 version: 3.27.0 @@ -4218,7 +4224,7 @@ importers: version: 0.19.2(@playwright/test@1.40.1)(typescript@5.3.2) '@wordpress/e2e-tests': specifier: ^4.9.2 - version: 4.9.2(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(jest@29.7.0)(puppeteer-core@21.6.0)(puppeteer@17.1.3)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2) + version: 4.9.2(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(jest@29.7.0)(puppeteer-core@21.6.0)(puppeteer@17.1.3)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2) '@wordpress/element': specifier: 5.22.0 version: 5.22.0 @@ -4245,7 +4251,7 @@ importers: version: 1.4.0(wp-prettier@2.8.5) '@wordpress/scripts': specifier: 24.6.0 - version: 24.6.0(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2) + version: 24.6.0(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2) '@wordpress/stylelint-config': specifier: ^21.36.0 version: 21.36.0(postcss@8.4.32)(stylelint@14.16.1) @@ -4272,13 +4278,13 @@ importers: version: 4.1.2 circular-dependency-plugin: specifier: 5.2.2 - version: 5.2.2(webpack@5.88.2) + version: 5.2.2(webpack@5.91.0) commander: specifier: 11.0.0 version: 11.0.0 copy-webpack-plugin: specifier: 11.0.0 - version: 11.0.0(webpack@5.88.2) + version: 11.0.0(webpack@5.91.0) core-js: specifier: 3.25.0 version: 3.25.0 @@ -4290,7 +4296,7 @@ importers: version: 7.0.3 css-loader: specifier: ^6.8.1 - version: 6.8.1(webpack@5.88.2) + version: 6.8.1(webpack@5.91.0) cssnano: specifier: 5.1.12 version: 5.1.12(postcss@8.4.32) @@ -4301,17 +4307,17 @@ importers: specifier: ^16.3.1 version: 16.3.1 eslint-import-resolver-typescript: - specifier: 3.2.4 - version: 3.2.4(eslint-plugin-import@2.28.1)(eslint@8.55.0) + specifier: 3.6.1 + version: 3.6.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-webpack@0.13.2)(eslint-plugin-import@2.28.1)(eslint@8.55.0) eslint-import-resolver-webpack: specifier: 0.13.2 - version: 0.13.2(eslint-plugin-import@2.28.1)(webpack@5.88.2) + version: 0.13.2(eslint-plugin-import@2.28.1)(webpack@5.91.0) eslint-plugin-import: specifier: 2.28.1 - version: 2.28.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) + version: 2.28.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) eslint-plugin-playwright: - specifier: 0.15.3 - version: 0.15.3(eslint@8.55.0) + specifier: 1.6.0 + version: 1.6.0(eslint@8.55.0) eslint-plugin-rulesdir: specifier: ^0.2.2 version: 0.2.2 @@ -4328,11 +4334,8 @@ importers: specifier: 6.1.1 version: 6.1.1 fast-xml-parser: - specifier: 4.2.4 - version: 4.2.4 - follow-redirects: - specifier: 1.15.1 - version: 1.15.1(debug@4.3.4) + specifier: 4.2.5 + version: 4.2.5 fs-extra: specifier: 11.1.1 version: 11.1.1 @@ -4386,7 +4389,7 @@ importers: version: 2.0.0 mini-css-extract-plugin: specifier: 2.7.6 - version: 2.7.6(webpack@5.88.2) + version: 2.7.6(webpack@5.91.0) patch-package: specifier: 6.4.7 version: 6.4.7 @@ -4398,13 +4401,13 @@ importers: version: 4.1.0 postcss-loader: specifier: 4.3.0 - version: 4.3.0(postcss@8.4.32)(webpack@5.88.2) + version: 4.3.0(postcss@8.4.32)(webpack@5.91.0) prettier: specifier: npm:wp-prettier@^2.8.5 version: /wp-prettier@2.8.5 progress-bar-webpack-plugin: specifier: 2.1.0 - version: 2.1.0(webpack@5.88.2) + version: 2.1.0(webpack@5.91.0) promptly: specifier: 3.2.0 version: 3.2.0 @@ -4416,7 +4419,7 @@ importers: version: 5.4.3 react-docgen-typescript-plugin: specifier: ^1.0.5 - version: 1.0.5(typescript@5.3.2)(webpack@5.88.2) + version: 1.0.5(typescript@5.3.2)(webpack@5.91.0) react-test-renderer: specifier: 17.0.2 version: 17.0.2(react@17.0.2) @@ -4434,7 +4437,7 @@ importers: version: 4.1.1 sass-loader: specifier: ^10.5.0 - version: 10.5.0(sass@1.69.5)(webpack@5.88.2) + version: 10.5.0(sass@1.69.5)(webpack@5.91.0) storybook: specifier: ^7.6.4 version: 7.6.4 @@ -4446,7 +4449,7 @@ importers: version: 14.16.1 terser-webpack-plugin: specifier: 5.3.6 - version: 5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.88.2) + version: 5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.91.0) typescript: specifier: 5.3.2 version: 5.3.2 @@ -4454,14 +4457,14 @@ importers: specifier: 3.10.0 version: 3.10.0 webpack: - specifier: 5.88.2 - version: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + specifier: 5.91.0 + version: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) webpack-bundle-analyzer: specifier: 4.7.0 version: 4.7.0 webpack-cli: specifier: 5.1.4 - version: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.88.2) + version: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.91.0) wireit: specifier: 0.14.3 version: 0.14.3 @@ -4472,6 +4475,12 @@ importers: specifier: 0.2.0 version: 0.2.0 + plugins/woocommerce-blocks/bin/eslint-plugin-woocommerce: + devDependencies: + eslint: + specifier: ^8.55.0 + version: 8.55.0 + plugins/woocommerce/client/legacy: dependencies: sourcebuster: @@ -4578,8 +4587,8 @@ importers: specifier: ^3.21.0 version: 3.21.0 uuid: - specifier: ^8.3.2 - version: 8.3.2 + specifier: ^9.0.1 + version: 9.0.1 devDependencies: '@types/jest': specifier: ^27.5.2 @@ -4742,8 +4751,8 @@ importers: specifier: ^9.0.3 version: 9.0.3 octokit: - specifier: ^2.1.0 - version: 2.1.0 + specifier: ^3.1.2 + version: 3.1.2 ora: specifier: ^5.4.1 version: 5.4.1 @@ -5002,7 +5011,7 @@ importers: version: 5.3.3 webpack: specifier: ^5.89.0 - version: 5.89.0(webpack-cli@3.3.12) + version: 5.89.0(webpack-cli@4.10.0) wireit: specifier: 0.14.3 version: 0.14.3 @@ -5063,6 +5072,10 @@ packages: /@ariakit/core@0.3.8: resolution: {integrity: sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==} + /@ariakit/core@0.4.5: + resolution: {integrity: sha512-e294+bEcyzt/H/kO4fS5/czLAlkF7PY+Kul3q2z54VY+GGay8NlVs9UezAB7L4jUBlYRAXwp7/1Sq3R7b+MZ7w==} + dev: false + /@ariakit/react-core@0.3.9(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-K1Rcxr6FpF0n3L7Uvo+e5hm+zqoZmXLRcYF/skI+/j+ole+uNbnsnfGhG1avqJlklqH4bmkFkjZzmMdOnUC0Ig==} peerDependencies: @@ -5075,6 +5088,19 @@ packages: react-dom: 17.0.2(react@17.0.2) use-sync-external-store: 1.2.0(react@17.0.2) + /@ariakit/react-core@0.4.5(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-ciTYPwpj/+mdA+EstveEnoygbx5e4PXQJxfkLKy4lkTkDJJUS9GcbYhdnIFJVUta6P1YFvzkIKo+/y9mcbMKJg==} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.0 || ^18.0.0 + dependencies: + '@ariakit/core': 0.4.5 + '@floating-ui/dom': 1.5.3 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + use-sync-external-store: 1.2.0(react@17.0.2) + dev: false + /@ariakit/react@0.3.9(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-gC+gibh2go8wvBqzYXavlHKwAfmee5GUMrPSQ9WBBLIfm9nQElujxcHJydaRx+ULR5FbOnbZVC3vU2ic8hSrNw==} peerDependencies: @@ -5085,6 +5111,17 @@ packages: react: 17.0.2 react-dom: 17.0.2(react@17.0.2) + /@ariakit/react@0.4.5(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-GUHxaOY1JZrJUHkuV20IY4NWcgknhqTQM0qCQcVZDCi+pJiWchUjTG+UyIr/Of02hU569qnQ7yovskCf+V3tNg==} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.0 || ^18.0.0 + dependencies: + '@ariakit/react-core': 0.4.5(react-dom@17.0.2)(react@17.0.2) + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + /@automattic/calypso-color-schemes@2.1.1: resolution: {integrity: sha512-X5gmQEDJVtw8N9NARgZGM/pmalfapV8ZyRzEn2o0sCLmTAXGYg6A28ucLCQdBIn1l9t2rghBDFkY71vyqjyyFQ==} dev: false @@ -12313,19 +12350,17 @@ packages: - supports-color dev: true - /@octokit/app@13.1.8: - resolution: {integrity: sha512-bCncePMguVyFpdBbnceFKfmPOuUD94T189GuQ0l00ZcQ+mX4hyPqnaWJlsXE2HSdA71eV7p8GPDZ+ErplTkzow==} - engines: {node: '>= 14'} + /@octokit/app@14.1.0: + resolution: {integrity: sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-app': 4.0.13 - '@octokit/auth-unauthenticated': 3.0.5 - '@octokit/core': 4.2.4 - '@octokit/oauth-app': 4.2.4 - '@octokit/plugin-paginate-rest': 6.1.2(@octokit/core@4.2.4) - '@octokit/types': 9.3.2 - '@octokit/webhooks': 10.9.2 - transitivePeerDependencies: - - encoding + '@octokit/auth-app': 6.1.1 + '@octokit/auth-unauthenticated': 5.0.1 + '@octokit/core': 5.2.0 + '@octokit/oauth-app': 6.1.0 + '@octokit/plugin-paginate-rest': 9.2.1(@octokit/core@5.2.0) + '@octokit/types': 12.6.0 + '@octokit/webhooks': 12.2.0 dev: false /@octokit/auth-action@2.1.1: @@ -12336,62 +12371,54 @@ packages: '@octokit/types': 9.3.2 dev: true - /@octokit/auth-app@4.0.13: - resolution: {integrity: sha512-NBQkmR/Zsc+8fWcVIFrwDgNXS7f4XDrkd9LHdi9DPQw1NdGHLviLzRO2ZBwTtepnwHXW5VTrVU9eFGijMUqllg==} - engines: {node: '>= 14'} + /@octokit/auth-app@6.1.1: + resolution: {integrity: sha512-VrTtzRpyuT5nYGUWeGWQqH//hqEZDV+/yb6+w5wmWpmmUA1Tx950XsAc2mBBfvusfcdF2E7w8jZ1r1WwvfZ9pA==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-oauth-app': 5.0.6 - '@octokit/auth-oauth-user': 2.1.2 - '@octokit/request': 6.2.8 - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.2 + '@octokit/auth-oauth-app': 7.1.0 + '@octokit/auth-oauth-user': 4.1.0 + '@octokit/request': 8.4.0 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.4.1 deprecation: 2.3.1 - lru-cache: 9.1.2 - universal-github-app-jwt: 1.1.1 + lru-cache: 10.1.0 + universal-github-app-jwt: 1.1.2 universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding dev: false - /@octokit/auth-oauth-app@5.0.6: - resolution: {integrity: sha512-SxyfIBfeFcWd9Z/m1xa4LENTQ3l1y6Nrg31k2Dcb1jS5ov7pmwMJZ6OGX8q3K9slRgVpeAjNA1ipOAMHkieqyw==} - engines: {node: '>= 14'} + /@octokit/auth-oauth-app@7.1.0: + resolution: {integrity: sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-oauth-device': 4.0.5 - '@octokit/auth-oauth-user': 2.1.2 - '@octokit/request': 6.2.8 - '@octokit/types': 9.3.2 + '@octokit/auth-oauth-device': 6.1.0 + '@octokit/auth-oauth-user': 4.1.0 + '@octokit/request': 8.4.0 + '@octokit/types': 13.4.1 '@types/btoa-lite': 1.0.2 btoa-lite: 1.0.0 universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding dev: false - /@octokit/auth-oauth-device@4.0.5: - resolution: {integrity: sha512-XyhoWRTzf2ZX0aZ52a6Ew5S5VBAfwwx1QnC2Np6Et3MWQpZjlREIcbcvVZtkNuXp6Z9EeiSLSDUqm3C+aMEHzQ==} - engines: {node: '>= 14'} + /@octokit/auth-oauth-device@6.1.0: + resolution: {integrity: sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==} + engines: {node: '>= 18'} dependencies: - '@octokit/oauth-methods': 2.0.6 - '@octokit/request': 6.2.8 - '@octokit/types': 9.3.2 + '@octokit/oauth-methods': 4.1.0 + '@octokit/request': 8.4.0 + '@octokit/types': 13.4.1 universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding dev: false - /@octokit/auth-oauth-user@2.1.2: - resolution: {integrity: sha512-kkRqNmFe7s5GQcojE3nSlF+AzYPpPv7kvP/xYEnE57584pixaFBH8Vovt+w5Y3E4zWUEOxjdLItmBTFAWECPAg==} - engines: {node: '>= 14'} + /@octokit/auth-oauth-user@4.1.0: + resolution: {integrity: sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-oauth-device': 4.0.5 - '@octokit/oauth-methods': 2.0.6 - '@octokit/request': 6.2.8 - '@octokit/types': 9.3.2 + '@octokit/auth-oauth-device': 6.1.0 + '@octokit/oauth-methods': 4.1.0 + '@octokit/request': 8.4.0 + '@octokit/types': 13.4.1 btoa-lite: 1.0.0 universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding dev: false /@octokit/auth-token@2.5.0: @@ -12404,12 +12431,17 @@ packages: resolution: {integrity: sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==} engines: {node: '>= 14'} - /@octokit/auth-unauthenticated@3.0.5: - resolution: {integrity: sha512-yH2GPFcjrTvDWPwJWWCh0tPPtTL5SMgivgKPA+6v/XmYN6hGQkAto8JtZibSKOpf8ipmeYhLNWQ2UgW0GYILCw==} - engines: {node: '>= 14'} + /@octokit/auth-token@4.0.0: + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + dev: false + + /@octokit/auth-unauthenticated@5.0.1: + resolution: {integrity: sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==} + engines: {node: '>= 18'} dependencies: - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.2 + '@octokit/request-error': 5.1.0 + '@octokit/types': 12.6.0 dev: false /@octokit/core@3.6.0: @@ -12440,6 +12472,19 @@ packages: transitivePeerDependencies: - encoding + /@octokit/core@5.2.0: + resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.1.0 + '@octokit/request': 8.4.0 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.4.1 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.1 + dev: false + /@octokit/endpoint@6.0.12: resolution: {integrity: sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==} dependencies: @@ -12455,6 +12500,14 @@ packages: is-plain-object: 5.0.0 universal-user-agent: 6.0.1 + /@octokit/endpoint@9.0.5: + resolution: {integrity: sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 13.4.1 + universal-user-agent: 6.0.1 + dev: false + /@octokit/graphql-schema@14.45.1: resolution: {integrity: sha512-4rrzuEOz6Df3Xm6z9UUEr+7G7Q/vlcnHAJW8SMz57Fr3fp8yPnXK9amJr4QISHZptF7d91WMrWK5UUbjPhBYMw==} dependencies: @@ -12481,39 +12534,43 @@ packages: transitivePeerDependencies: - encoding - /@octokit/oauth-app@4.2.4: - resolution: {integrity: sha512-iuOVFrmm5ZKNavRtYu5bZTtmlKLc5uVgpqTfMEqYYf2OkieV6VdxKZAb5qLVdEPL8LU2lMWcGpavPBV835cgoA==} - engines: {node: '>= 14'} + /@octokit/graphql@7.1.0: + resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} + engines: {node: '>= 18'} dependencies: - '@octokit/auth-oauth-app': 5.0.6 - '@octokit/auth-oauth-user': 2.1.2 - '@octokit/auth-unauthenticated': 3.0.5 - '@octokit/core': 4.2.4 - '@octokit/oauth-authorization-url': 5.0.0 - '@octokit/oauth-methods': 2.0.6 + '@octokit/request': 8.4.0 + '@octokit/types': 13.4.1 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/oauth-app@6.1.0: + resolution: {integrity: sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-oauth-app': 7.1.0 + '@octokit/auth-oauth-user': 4.1.0 + '@octokit/auth-unauthenticated': 5.0.1 + '@octokit/core': 5.2.0 + '@octokit/oauth-authorization-url': 6.0.2 + '@octokit/oauth-methods': 4.1.0 '@types/aws-lambda': 8.10.130 - fromentries: 1.3.2 universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding dev: false - /@octokit/oauth-authorization-url@5.0.0: - resolution: {integrity: sha512-y1WhN+ERDZTh0qZ4SR+zotgsQUE1ysKnvBt1hvDRB2WRzYtVKQjn97HEPzoehh66Fj9LwNdlZh+p6TJatT0zzg==} - engines: {node: '>= 14'} + /@octokit/oauth-authorization-url@6.0.2: + resolution: {integrity: sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==} + engines: {node: '>= 18'} dev: false - /@octokit/oauth-methods@2.0.6: - resolution: {integrity: sha512-l9Uml2iGN2aTWLZcm8hV+neBiFXAQ9+3sKiQe/sgumHlL6HDg0AQ8/l16xX/5jJvfxueqTW5CWbzd0MjnlfHZw==} - engines: {node: '>= 14'} + /@octokit/oauth-methods@4.1.0: + resolution: {integrity: sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==} + engines: {node: '>= 18'} dependencies: - '@octokit/oauth-authorization-url': 5.0.0 - '@octokit/request': 6.2.8 - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.2 + '@octokit/oauth-authorization-url': 6.0.2 + '@octokit/request': 8.4.0 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.4.1 btoa-lite: 1.0.0 - transitivePeerDependencies: - - encoding dev: false /@octokit/openapi-types@12.11.0: @@ -12522,6 +12579,23 @@ packages: /@octokit/openapi-types@18.1.1: resolution: {integrity: sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==} + /@octokit/openapi-types@20.0.0: + resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + dev: false + + /@octokit/openapi-types@22.1.0: + resolution: {integrity: sha512-pGUdSP+eEPfZiQHNkZI0U01HLipxncisdJQB4G//OAmfeO8sqTQ9KRa0KF03TUPCziNsoXUrTg4B2Q1EX++T0Q==} + dev: false + + /@octokit/plugin-paginate-graphql@4.0.1(@octokit/core@5.2.0): + resolution: {integrity: sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + dependencies: + '@octokit/core': 5.2.0 + dev: false + /@octokit/plugin-paginate-rest@2.21.3(@octokit/core@3.6.0): resolution: {integrity: sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==} peerDependencies: @@ -12541,6 +12615,16 @@ packages: '@octokit/tsconfig': 1.0.2 '@octokit/types': 9.3.2 + /@octokit/plugin-paginate-rest@9.2.1(@octokit/core@5.2.0): + resolution: {integrity: sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + dependencies: + '@octokit/core': 5.2.0 + '@octokit/types': 12.6.0 + dev: false + /@octokit/plugin-request-log@1.0.4(@octokit/core@3.6.0): resolution: {integrity: sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==} peerDependencies: @@ -12557,6 +12641,16 @@ packages: '@octokit/core': 4.2.4 dev: false + /@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.2.0): + resolution: {integrity: sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + dependencies: + '@octokit/core': 5.2.0 + '@octokit/types': 12.6.0 + dev: false + /@octokit/plugin-rest-endpoint-methods@5.16.2(@octokit/core@3.6.0): resolution: {integrity: sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==} peerDependencies: @@ -12576,25 +12670,26 @@ packages: '@octokit/core': 4.2.4 '@octokit/types': 10.0.0 - /@octokit/plugin-retry@4.1.6(@octokit/core@4.2.4): - resolution: {integrity: sha512-obkYzIgEC75r8+9Pnfiiqy3y/x1bc3QLE5B7qvv9wi9Kj0R5tGQFC6QMBg1154WQ9lAVypuQDGyp3hNpp15gQQ==} - engines: {node: '>= 14'} + /@octokit/plugin-retry@6.0.1(@octokit/core@5.2.0): + resolution: {integrity: sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '>=3' + '@octokit/core': '>=5' dependencies: - '@octokit/core': 4.2.4 - '@octokit/types': 9.3.2 + '@octokit/core': 5.2.0 + '@octokit/request-error': 5.1.0 + '@octokit/types': 12.6.0 bottleneck: 2.19.5 dev: false - /@octokit/plugin-throttling@5.2.3(@octokit/core@4.2.4): - resolution: {integrity: sha512-C9CFg9mrf6cugneKiaI841iG8DOv6P5XXkjmiNNut+swePxQ7RWEdAZRp5rJoE1hjsIqiYcKa/ZkOQ+ujPI39Q==} - engines: {node: '>= 14'} + /@octokit/plugin-throttling@8.2.0(@octokit/core@5.2.0): + resolution: {integrity: sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==} + engines: {node: '>= 18'} peerDependencies: - '@octokit/core': ^4.0.0 + '@octokit/core': ^5.0.0 dependencies: - '@octokit/core': 4.2.4 - '@octokit/types': 9.3.2 + '@octokit/core': 5.2.0 + '@octokit/types': 12.6.0 bottleneck: 2.19.5 dev: false @@ -12613,6 +12708,15 @@ packages: deprecation: 2.3.1 once: 1.4.0 + /@octokit/request-error@5.1.0: + resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 13.4.1 + deprecation: 2.3.1 + once: 1.4.0 + dev: false + /@octokit/request@5.6.3: resolution: {integrity: sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==} dependencies: @@ -12638,6 +12742,16 @@ packages: transitivePeerDependencies: - encoding + /@octokit/request@8.4.0: + resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/endpoint': 9.0.5 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.4.1 + universal-user-agent: 6.0.1 + dev: false + /@octokit/rest@18.12.0: resolution: {integrity: sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==} dependencies: @@ -12669,6 +12783,18 @@ packages: dependencies: '@octokit/openapi-types': 18.1.1 + /@octokit/types@12.6.0: + resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + dependencies: + '@octokit/openapi-types': 20.0.0 + dev: false + + /@octokit/types@13.4.1: + resolution: {integrity: sha512-Y73oOAzRBAUzR/iRAbGULzpNkX8vaxKCqEtg6K74Ff3w9f5apFnWtE/2nade7dMWWW3bS5Kkd6DJS4HF04xreg==} + dependencies: + '@octokit/openapi-types': 22.1.0 + dev: false + /@octokit/types@6.41.0: resolution: {integrity: sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==} dependencies: @@ -12679,22 +12805,22 @@ packages: dependencies: '@octokit/openapi-types': 18.1.1 - /@octokit/webhooks-methods@3.0.3: - resolution: {integrity: sha512-2vM+DCNTJ5vL62O5LagMru6XnYhV4fJslK+5YUkTa6rWlW2S+Tqs1lF9Wr9OGqHfVwpBj3TeztWfVON/eUoW1Q==} - engines: {node: '>= 14'} + /@octokit/webhooks-methods@4.1.0: + resolution: {integrity: sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==} + engines: {node: '>= 18'} dev: false - /@octokit/webhooks-types@6.11.0: - resolution: {integrity: sha512-AanzbulOHljrku1NGfafxdpTCfw2ENaWzH01N2vqQM+cUFbk868Cgh0xylz0JIM9BoKbfI++bdD6EYX0Q/UTEw==} + /@octokit/webhooks-types@7.4.0: + resolution: {integrity: sha512-FE2V+QZ2UYlh+9wWd5BPLNXG+J/XUD/PPq0ovS+nCcGX4+3qVbi3jYOmCTW48hg9SBBLtInx9+o7fFt4H5iP0Q==} dev: false - /@octokit/webhooks@10.9.2: - resolution: {integrity: sha512-hFVF/szz4l/Y/GQdKxNmQjUke0XJXK986p+ucIlubTGVPVtVtup5G1jarQfvCMBs9Fvlf9dvH8K83E4lefmofQ==} - engines: {node: '>= 14'} + /@octokit/webhooks@12.2.0: + resolution: {integrity: sha512-CyuLJ0/P7bKZ+kIYw+fnkeVdhUzNuDKgNSI7pU/m7Nod0T7kP+s4s2f0pNmG9HL8/RZN1S0ZWTDll3VTMrFLAw==} + engines: {node: '>= 18'} dependencies: - '@octokit/request-error': 3.0.3 - '@octokit/webhooks-methods': 3.0.3 - '@octokit/webhooks-types': 6.11.0 + '@octokit/request-error': 5.1.0 + '@octokit/webhooks-methods': 4.1.0 + '@octokit/webhooks-types': 7.4.0 aggregate-error: 3.1.0 dev: false @@ -12704,18 +12830,6 @@ packages: requiresBuild: true optional: true - /@pkgr/utils@2.4.2: - resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - dependencies: - cross-spawn: 7.0.3 - fast-glob: 3.3.2 - is-glob: 4.0.3 - open: 9.1.0 - picocolors: 1.0.0 - tslib: 2.6.2 - dev: true - /@playwright/test@1.40.1: resolution: {integrity: sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==} engines: {node: '>=16'} @@ -12724,7 +12838,7 @@ packages: playwright: 1.40.1 dev: true - /@pmmmwh/react-refresh-webpack-plugin@0.5.11(react-refresh@0.10.0)(webpack-dev-server@4.15.1)(webpack@5.89.0): + /@pmmmwh/react-refresh-webpack-plugin@0.5.11(react-refresh@0.10.0)(webpack-dev-server@4.15.1)(webpack@5.91.0): resolution: {integrity: sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==} engines: {node: '>= 10.13'} peerDependencies: @@ -12760,11 +12874,11 @@ packages: react-refresh: 0.10.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) - webpack-dev-server: 4.15.1(webpack-cli@4.10.0)(webpack@5.89.0) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + webpack-dev-server: 4.15.1(webpack-cli@4.10.0)(webpack@5.91.0) dev: true - /@pmmmwh/react-refresh-webpack-plugin@0.5.11(react-refresh@0.11.0)(webpack@5.89.0): + /@pmmmwh/react-refresh-webpack-plugin@0.5.11(react-refresh@0.11.0)(webpack@5.91.0): resolution: {integrity: sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==} engines: {node: '>= 10.13'} peerDependencies: @@ -12800,7 +12914,7 @@ packages: react-refresh: 0.11.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) dev: true /@pmmmwh/react-refresh-webpack-plugin@0.5.11(react-refresh@0.14.0)(webpack-dev-server@4.15.1)(webpack@5.89.0): @@ -12843,7 +12957,7 @@ packages: webpack-dev-server: 4.15.1(debug@4.3.4)(webpack-cli@4.10.0)(webpack@5.89.0) dev: true - /@pmmmwh/react-refresh-webpack-plugin@0.5.11(react-refresh@0.14.0)(webpack@5.89.0): + /@pmmmwh/react-refresh-webpack-plugin@0.5.11(react-refresh@0.14.0)(webpack@5.91.0): resolution: {integrity: sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==} engines: {node: '>= 10.13'} peerDependencies: @@ -12879,7 +12993,7 @@ packages: react-refresh: 0.14.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /@polka/url@1.0.0-next.24: @@ -14312,7 +14426,7 @@ packages: '@react-native-community/cli-tools': 12.1.1 chalk: 4.1.2 execa: 5.1.1 - fast-xml-parser: 4.2.4 + fast-xml-parser: 4.2.5 glob: 7.2.3 logkitty: 0.7.1 transitivePeerDependencies: @@ -14324,7 +14438,7 @@ packages: '@react-native-community/cli-tools': 12.1.1 chalk: 4.1.2 execa: 5.1.1 - fast-xml-parser: 4.2.4 + fast-xml-parser: 4.2.5 glob: 7.2.3 ora: 5.4.1 transitivePeerDependencies: @@ -15524,13 +15638,13 @@ packages: - '@types/react-dom' dev: true - /@storybook/addon-styling-webpack@0.0.5(webpack@5.88.2): + /@storybook/addon-styling-webpack@0.0.5(webpack@5.91.0): resolution: {integrity: sha512-8XdE3w+W7qxMeqnK/FDFipFN9XaVKUJFcHyM2ZBlhAAxVWMbTcifqw5lzqeK8jSAA/TUtpUp37gT2Bw/3k6slQ==} peerDependencies: webpack: ^5.0.0 dependencies: '@storybook/node-logger': 7.6.4 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /@storybook/addon-toolbars@7.5.2(@types/react-dom@18.0.10)(@types/react@17.0.71)(react-dom@17.0.2)(react@17.0.2): @@ -15889,28 +16003,28 @@ packages: '@storybook/store': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@storybook/theming': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@types/node': 16.18.68 - babel-loader: 8.3.0(@babel/core@7.23.5)(webpack@5.89.0) + babel-loader: 8.3.0(@babel/core@7.23.5)(webpack@5.91.0) babel-plugin-named-exports-order: 0.0.2 browser-assert: 1.2.1 case-sensitive-paths-webpack-plugin: 2.4.0 core-js: 3.34.0 - css-loader: 5.2.7(webpack@5.89.0) - fork-ts-checker-webpack-plugin: 6.5.3(eslint@8.55.0)(typescript@5.3.3)(webpack@5.89.0) + css-loader: 5.2.7(webpack@5.91.0) + fork-ts-checker-webpack-plugin: 6.5.3(eslint@8.55.0)(typescript@5.3.3)(webpack@5.91.0) glob: 7.2.3 glob-promise: 3.4.0(glob@7.2.3) - html-webpack-plugin: 5.5.4(webpack@5.89.0) + html-webpack-plugin: 5.5.4(webpack@5.91.0) path-browserify: 1.0.1 process: 0.11.10 react: 17.0.2 react-dom: 17.0.2(react@17.0.2) stable: 0.1.8 - style-loader: 2.0.0(webpack@5.89.0) - terser-webpack-plugin: 5.3.6(webpack@5.89.0) + style-loader: 2.0.0(webpack@5.91.0) + terser-webpack-plugin: 5.3.6(webpack@5.91.0) ts-dedent: 2.2.0 typescript: 5.3.3 util-deprecate: 1.0.2 - webpack: 5.89.0(webpack-cli@3.3.12) - webpack-dev-middleware: 4.3.0(webpack@5.89.0) + webpack: 5.91.0(webpack-cli@3.3.12) + webpack-dev-middleware: 4.3.0(webpack@5.91.0) webpack-hot-middleware: 2.25.4 webpack-virtual-modules: 0.4.6 transitivePeerDependencies: @@ -15944,30 +16058,30 @@ packages: '@swc/core': 1.3.100 '@types/node': 18.19.3 '@types/semver': 7.5.6 - babel-loader: 9.1.3(@babel/core@7.23.5)(webpack@5.89.0) + babel-loader: 9.1.3(@babel/core@7.23.5)(webpack@5.91.0) browser-assert: 1.2.1 case-sensitive-paths-webpack-plugin: 2.4.0 constants-browserify: 1.0.0 - css-loader: 6.8.1(webpack@5.89.0) + css-loader: 6.8.1(webpack@5.91.0) es-module-lexer: 1.4.1 express: 4.18.2 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.2)(webpack@5.89.0) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.3.2)(webpack@5.91.0) fs-extra: 11.1.1 - html-webpack-plugin: 5.5.4(webpack@5.89.0) + html-webpack-plugin: 5.5.4(webpack@5.91.0) magic-string: 0.30.5 path-browserify: 1.0.1 process: 0.11.10 semver: 7.5.4 - style-loader: 3.3.3(webpack@5.89.0) - swc-loader: 0.2.3(@swc/core@1.3.100)(webpack@5.89.0) - terser-webpack-plugin: 5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.89.0) + style-loader: 3.3.3(webpack@5.91.0) + swc-loader: 0.2.3(@swc/core@1.3.100)(webpack@5.91.0) + terser-webpack-plugin: 5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.91.0) ts-dedent: 2.2.0 typescript: 5.3.2 url: 0.11.3 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-dev-middleware: 6.1.1(webpack@5.89.0) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-dev-middleware: 6.1.1(webpack@5.91.0) webpack-hot-middleware: 2.25.4 webpack-virtual-modules: 0.5.0 transitivePeerDependencies: @@ -16284,7 +16398,7 @@ packages: webpack: 4.47.0(webpack-cli@3.3.12) dev: true - /@storybook/core-client@6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.89.0): + /@storybook/core-client@6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.91.0): resolution: {integrity: sha512-j4UqRv16EwavjFUbFnB1CTdkJf70/yzAZNs78OZuTeMHAbTD8AuKpVZ/MBniymll11AIYV0ue7Hr1cwxYuTWDA==} peerDependencies: react: ^17.0.2 @@ -16318,7 +16432,7 @@ packages: typescript: 5.3.3 unfetch: 4.2.0 util-deprecate: 1.0.2 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) dev: true /@storybook/core-client@7.5.2: @@ -16706,7 +16820,7 @@ packages: - supports-color dev: true - /@storybook/core@6.5.17-alpha.0(@storybook/builder-webpack5@6.5.17-alpha.0)(@storybook/manager-webpack5@6.5.17-alpha.0)(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.89.0): + /@storybook/core@6.5.17-alpha.0(@storybook/builder-webpack5@6.5.17-alpha.0)(@storybook/manager-webpack5@6.5.17-alpha.0)(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.91.0): resolution: {integrity: sha512-M5ccC7Qe0TWnX7KG2le9WRAnvaasBKEjxH8o0QvjhnxbUZxPGhEC9OjuR2QTZcPsA+WDRvPukK2f2ugqLpIOwA==} peerDependencies: '@storybook/builder-webpack5': '*' @@ -16724,13 +16838,13 @@ packages: optional: true dependencies: '@storybook/builder-webpack5': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3) - '@storybook/core-client': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.89.0) + '@storybook/core-client': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.91.0) '@storybook/core-server': 6.5.17-alpha.0(@storybook/builder-webpack5@6.5.17-alpha.0)(@storybook/manager-webpack5@6.5.17-alpha.0)(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3) '@storybook/manager-webpack5': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) typescript: 5.3.3 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) transitivePeerDependencies: - '@storybook/mdx2-csf' - bluebird @@ -16744,7 +16858,7 @@ packages: - webpack-command dev: true - /@storybook/core@6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack-cli@3.3.12)(webpack@5.89.0): + /@storybook/core@6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack-cli@3.3.12)(webpack@5.91.0): resolution: {integrity: sha512-M5ccC7Qe0TWnX7KG2le9WRAnvaasBKEjxH8o0QvjhnxbUZxPGhEC9OjuR2QTZcPsA+WDRvPukK2f2ugqLpIOwA==} peerDependencies: '@storybook/builder-webpack5': '*' @@ -16761,12 +16875,12 @@ packages: typescript: optional: true dependencies: - '@storybook/core-client': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.89.0) + '@storybook/core-client': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.91.0) '@storybook/core-server': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack-cli@3.3.12) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) typescript: 5.3.3 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) transitivePeerDependencies: - '@storybook/mdx2-csf' - bluebird @@ -17095,21 +17209,21 @@ packages: '@babel/plugin-transform-template-literals': 7.23.3(@babel/core@7.23.5) '@babel/preset-react': 7.23.3(@babel/core@7.23.5) '@storybook/addons': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) - '@storybook/core-client': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.89.0) + '@storybook/core-client': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.91.0) '@storybook/core-common': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack-cli@3.3.12) '@storybook/node-logger': 6.5.17-alpha.0 '@storybook/theming': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@storybook/ui': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@types/node': 16.18.68 - babel-loader: 8.3.0(@babel/core@7.23.5)(webpack@5.89.0) + babel-loader: 8.3.0(@babel/core@7.23.5)(webpack@5.91.0) case-sensitive-paths-webpack-plugin: 2.4.0 chalk: 4.1.2 core-js: 3.34.0 - css-loader: 5.2.7(webpack@5.89.0) + css-loader: 5.2.7(webpack@5.91.0) express: 4.18.2 find-up: 5.0.0 fs-extra: 9.1.0 - html-webpack-plugin: 5.5.4(webpack@5.89.0) + html-webpack-plugin: 5.5.4(webpack@5.91.0) node-fetch: 2.7.0 process: 0.11.10 react: 17.0.2 @@ -17117,14 +17231,14 @@ packages: read-pkg-up: 7.0.1 regenerator-runtime: 0.13.11 resolve-from: 5.0.0 - style-loader: 2.0.0(webpack@5.89.0) + style-loader: 2.0.0(webpack@5.91.0) telejson: 6.0.8 - terser-webpack-plugin: 5.3.6(webpack@5.89.0) + terser-webpack-plugin: 5.3.6(webpack@5.91.0) ts-dedent: 2.2.0 typescript: 5.3.3 util-deprecate: 1.0.2 - webpack: 5.89.0(webpack-cli@3.3.12) - webpack-dev-middleware: 4.3.0(webpack@5.89.0) + webpack: 5.91.0(webpack-cli@3.3.12) + webpack-dev-middleware: 4.3.0(webpack@5.91.0) webpack-virtual-modules: 0.4.6 transitivePeerDependencies: - '@swc/core' @@ -17233,12 +17347,12 @@ packages: '@babel/core': 7.23.2 '@babel/preset-flow': 7.23.3(@babel/core@7.23.2) '@babel/preset-react': 7.23.3(@babel/core@7.23.2) - '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.14.0)(webpack@5.89.0) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.14.0)(webpack@5.91.0) '@storybook/core-webpack': 7.6.4 '@storybook/docs-tools': 7.6.4 '@storybook/node-logger': 7.6.4 '@storybook/react': 7.6.4(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.2)(webpack@5.89.0) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.3.2)(webpack@5.91.0) '@types/node': 18.19.3 '@types/semver': 7.5.6 babel-plugin-add-react-displayname: 0.0.5 @@ -17250,7 +17364,7 @@ packages: react-refresh: 0.14.0 semver: 7.5.4 typescript: 5.3.2 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) transitivePeerDependencies: - '@swc/core' - '@types/webpack' @@ -17334,7 +17448,7 @@ packages: resolution: {integrity: sha512-p9xIvNkgXgTpSRphOMV9KpIiNdkymH61jBg3B0XyoF6IfM1S2/mQGvC89lCVz1dMGk2SrH4g87/WcOapkU5ArA==} dev: true - /@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.3.2)(webpack@5.89.0): + /@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.3.2)(webpack@5.91.0): resolution: {integrity: sha512-KUqXC3oa9JuQ0kZJLBhVdS4lOneKTOopnNBK4tUAgoxWQ3u/IjzdueZjFr7gyBrXMoU6duutk3RQR9u8ZpYJ4Q==} peerDependencies: typescript: '>= 4.x' @@ -17348,12 +17462,12 @@ packages: react-docgen-typescript: 2.2.2(typescript@5.3.2) tslib: 2.6.2 typescript: 5.3.2 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) transitivePeerDependencies: - supports-color dev: true - /@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.630821.0(typescript@5.3.3)(webpack@5.89.0): + /@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.630821.0(typescript@5.3.3)(webpack@5.91.0): resolution: {integrity: sha512-adrUdN/hb/bzRBmSJtHBEwoPpZzmMbr9WIEp83As69j0hkSa2Rp/Fvp+f97A2FyEx0+skiSX8ENLnwuup+5yuA==} peerDependencies: typescript: '>= 4.x' @@ -17367,7 +17481,7 @@ packages: react-docgen-typescript: 2.2.2(typescript@5.3.3) tslib: 2.6.2 typescript: 5.3.3 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) transitivePeerDependencies: - supports-color dev: true @@ -17461,15 +17575,15 @@ packages: '@babel/core': 7.23.5 '@babel/preset-flow': 7.23.3(@babel/core@7.23.5) '@babel/preset-react': 7.23.3(@babel/core@7.23.5) - '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.11.0)(webpack@5.89.0) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.11.0)(webpack@5.91.0) '@storybook/addons': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@storybook/client-logger': 6.5.17-alpha.0 - '@storybook/core': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack-cli@3.3.12)(webpack@5.89.0) + '@storybook/core': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack-cli@3.3.12)(webpack@5.91.0) '@storybook/core-common': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack-cli@3.3.12) '@storybook/csf': 0.0.2--canary.4566f4d.1 '@storybook/docs-tools': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@storybook/node-logger': 6.5.17-alpha.0 - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.630821.0(typescript@5.3.3)(webpack@5.89.0) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.630821.0(typescript@5.3.3)(webpack@5.91.0) '@storybook/semver': 7.3.2 '@storybook/store': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@types/estree': 0.0.51 @@ -17497,7 +17611,7 @@ packages: ts-dedent: 2.2.0 typescript: 5.3.3 util-deprecate: 1.0.2 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) transitivePeerDependencies: - '@storybook/mdx2-csf' - '@swc/core' @@ -17551,17 +17665,17 @@ packages: '@babel/core': 7.23.6 '@babel/preset-flow': 7.23.3(@babel/core@7.23.6) '@babel/preset-react': 7.23.3(@babel/core@7.23.6) - '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.11.0)(webpack@5.89.0) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.11.0)(webpack@5.91.0) '@storybook/addons': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@storybook/builder-webpack5': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3) '@storybook/client-logger': 6.5.17-alpha.0 - '@storybook/core': 6.5.17-alpha.0(@storybook/builder-webpack5@6.5.17-alpha.0)(@storybook/manager-webpack5@6.5.17-alpha.0)(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.89.0) + '@storybook/core': 6.5.17-alpha.0(@storybook/builder-webpack5@6.5.17-alpha.0)(@storybook/manager-webpack5@6.5.17-alpha.0)(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack@5.91.0) '@storybook/core-common': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3)(webpack-cli@3.3.12) '@storybook/csf': 0.0.2--canary.4566f4d.1 '@storybook/docs-tools': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@storybook/manager-webpack5': 6.5.17-alpha.0(eslint@8.55.0)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.3) '@storybook/node-logger': 6.5.17-alpha.0 - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.630821.0(typescript@5.3.3)(webpack@5.89.0) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.630821.0(typescript@5.3.3)(webpack@5.91.0) '@storybook/semver': 7.3.2 '@storybook/store': 6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2) '@types/estree': 0.0.51 @@ -17589,7 +17703,7 @@ packages: ts-dedent: 2.2.0 typescript: 5.3.3 util-deprecate: 1.0.2 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) transitivePeerDependencies: - '@storybook/mdx2-csf' - '@swc/core' @@ -18935,16 +19049,17 @@ packages: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: '@types/eslint': 8.44.8 - '@types/estree': 0.0.51 + '@types/estree': 1.0.5 /@types/eslint@8.44.8: resolution: {integrity: sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==} dependencies: - '@types/estree': 0.0.51 + '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 /@types/estree@0.0.51: resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} + dev: true /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -19202,11 +19317,6 @@ packages: undici-types: 5.26.5 dev: true - /@types/node@20.10.4: - resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==} - dependencies: - undici-types: 5.26.5 - /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true @@ -20305,10 +20415,6 @@ packages: /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - /@unts/get-tsconfig@4.1.1: - resolution: {integrity: sha512-8mPf1bBzF2S+fyuyYOQWjDcaJTTgJ14UAnXW9I3KwrqioRWG1byRXHwciYdqXpbdOiu7Fg4WJbymBIakGk+aMA==} - dev: true - /@use-gesture/core@10.3.0: resolution: {integrity: sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A==} @@ -20333,6 +20439,13 @@ packages: '@webassemblyjs/helper-numbers': 1.11.6 '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + /@webassemblyjs/ast@1.12.1: + resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} + dependencies: + '@webassemblyjs/helper-numbers': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + dev: true + /@webassemblyjs/ast@1.9.0: resolution: {integrity: sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==} dependencies: @@ -20370,6 +20483,10 @@ packages: /@webassemblyjs/helper-buffer@1.11.6: resolution: {integrity: sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==} + /@webassemblyjs/helper-buffer@1.12.1: + resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} + dev: true + /@webassemblyjs/helper-buffer@1.9.0: resolution: {integrity: sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==} dev: true @@ -20433,6 +20550,15 @@ packages: '@webassemblyjs/helper-wasm-bytecode': 1.11.6 '@webassemblyjs/wasm-gen': 1.11.6 + /@webassemblyjs/helper-wasm-section@1.12.1: + resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/wasm-gen': 1.12.1 + dev: true + /@webassemblyjs/helper-wasm-section@1.9.0: resolution: {integrity: sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==} dependencies: @@ -20512,6 +20638,19 @@ packages: '@webassemblyjs/wasm-parser': 1.11.6 '@webassemblyjs/wast-printer': 1.11.6 + /@webassemblyjs/wasm-edit@1.12.1: + resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/helper-wasm-section': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-opt': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + '@webassemblyjs/wast-printer': 1.12.1 + dev: true + /@webassemblyjs/wasm-edit@1.9.0: resolution: {integrity: sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==} dependencies: @@ -20544,6 +20683,16 @@ packages: '@webassemblyjs/leb128': 1.11.6 '@webassemblyjs/utf8': 1.11.6 + /@webassemblyjs/wasm-gen@1.12.1: + resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + dev: true + /@webassemblyjs/wasm-gen@1.9.0: resolution: {integrity: sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==} dependencies: @@ -20571,6 +20720,15 @@ packages: '@webassemblyjs/wasm-gen': 1.11.6 '@webassemblyjs/wasm-parser': 1.11.6 + /@webassemblyjs/wasm-opt@1.12.1: + resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + dev: true + /@webassemblyjs/wasm-opt@1.9.0: resolution: {integrity: sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==} dependencies: @@ -20601,6 +20759,17 @@ packages: '@webassemblyjs/leb128': 1.11.6 '@webassemblyjs/utf8': 1.11.6 + /@webassemblyjs/wasm-parser@1.12.1: + resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-api-error': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + dev: true + /@webassemblyjs/wasm-parser@1.9.0: resolution: {integrity: sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==} dependencies: @@ -20636,6 +20805,13 @@ packages: '@webassemblyjs/ast': 1.11.6 '@xtuc/long': 4.2.2 + /@webassemblyjs/wast-printer@1.12.1: + resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@xtuc/long': 4.2.2 + dev: true + /@webassemblyjs/wast-printer@1.9.0: resolution: {integrity: sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==} dependencies: @@ -20653,15 +20829,25 @@ packages: webpack: 5.89.0(webpack-cli@4.10.0) webpack-cli: 4.10.0(webpack-bundle-analyzer@3.9.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) - /@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.88.2): + /@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0)(webpack@5.91.0): + resolution: {integrity: sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==} + peerDependencies: + webpack: 4.x.x || 5.x.x + webpack-cli: 4.x.x + dependencies: + webpack: 5.91.0(uglify-js@3.17.4)(webpack-cli@4.10.0) + webpack-cli: 4.10.0(webpack@5.91.0) + dev: true + + /@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.91.0): resolution: {integrity: sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==} engines: {node: '>=14.15.0'} peerDependencies: webpack: 5.x.x webpack-cli: 5.x.x dependencies: - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.88.2) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.91.0) dev: true /@webpack-cli/info@1.5.0(webpack-cli@4.10.0): @@ -20672,15 +20858,15 @@ packages: envinfo: 7.11.0 webpack-cli: 4.10.0(webpack-bundle-analyzer@3.9.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) - /@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.88.2): + /@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.91.0): resolution: {integrity: sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==} engines: {node: '>=14.15.0'} peerDependencies: webpack: 5.x.x webpack-cli: 5.x.x dependencies: - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.88.2) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.91.0) dev: true /@webpack-cli/serve@1.7.0(webpack-cli@4.10.0): @@ -20692,7 +20878,7 @@ packages: webpack-dev-server: optional: true dependencies: - webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack@5.89.0) + webpack-cli: 4.10.0(webpack@5.91.0) dev: true /@webpack-cli/serve@1.7.0(webpack-cli@4.10.0)(webpack-dev-server@4.15.1): @@ -20707,7 +20893,7 @@ packages: webpack-cli: 4.10.0(webpack-bundle-analyzer@3.9.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) webpack-dev-server: 4.15.1(debug@4.3.4)(webpack-cli@4.10.0)(webpack@5.89.0) - /@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.88.2): + /@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.91.0): resolution: {integrity: sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==} engines: {node: '>=14.15.0'} peerDependencies: @@ -20718,8 +20904,8 @@ packages: webpack-dev-server: optional: true dependencies: - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.88.2) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.91.0) dev: true /@wojtekmaj/enzyme-adapter-react-17@0.6.7(enzyme@3.11.0)(react-dom@17.0.2)(react@17.0.2): @@ -22514,25 +22700,25 @@ packages: webpack: 5.89.0(webpack-cli@3.3.12) webpack-sources: 3.2.3 - /@wordpress/dependency-extraction-webpack-plugin@4.28.0(webpack@5.88.2): - resolution: {integrity: sha512-/qfcNv6+cp+8guuNO88/inlFWf21UqeqxxcmcD+41rRfDr8+xqsHMmLwRvSip4KbT8veR2i7Gvem5H5E5x8uVA==} - engines: {node: '>=14'} + /@wordpress/dependency-extraction-webpack-plugin@3.7.0(webpack@5.91.0): + resolution: {integrity: sha512-SHyp88D1ICSaRVMfs/kKEicjKXWf1y2wecUeZIiMtkfAi8Bnk3JsnUo11LH7drJIXfjmDoer2B2rrBMZmRm8VA==} + engines: {node: '>=12'} peerDependencies: webpack: ^4.8.3 || ^5.0.0 dependencies: - json2php: 0.0.7 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + json2php: 0.0.4 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) webpack-sources: 3.2.3 dev: true - /@wordpress/dependency-extraction-webpack-plugin@4.28.0(webpack@5.89.0): + /@wordpress/dependency-extraction-webpack-plugin@4.28.0(webpack@5.91.0): resolution: {integrity: sha512-/qfcNv6+cp+8guuNO88/inlFWf21UqeqxxcmcD+41rRfDr8+xqsHMmLwRvSip4KbT8veR2i7Gvem5H5E5x8uVA==} engines: {node: '>=14'} peerDependencies: webpack: ^4.8.3 || ^5.0.0 dependencies: json2php: 0.0.7 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) webpack-sources: 3.2.3 dev: true @@ -22775,7 +22961,7 @@ packages: - encoding dev: false - /@wordpress/e2e-tests@4.9.2(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(jest@29.7.0)(puppeteer-core@21.6.0)(puppeteer@17.1.3)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2): + /@wordpress/e2e-tests@4.9.2(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(jest@29.7.0)(puppeteer-core@21.6.0)(puppeteer@17.1.3)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2): resolution: {integrity: sha512-2uhAC3/HyJjVSyC0j3ClxhLeuU7IkZouRDOaudTETbAo2c/0EC2mlEsULF/fUJEluLtIDWaL8GYdP4tD/KvJ8Q==} engines: {node: '>=12'} peerDependencies: @@ -22787,7 +22973,7 @@ packages: '@wordpress/e2e-test-utils': 7.11.0(jest@29.7.0)(puppeteer-core@21.6.0) '@wordpress/jest-console': 5.4.0(jest@29.7.0) '@wordpress/jest-puppeteer-axe': 4.1.0(jest@29.7.0)(puppeteer@17.1.3) - '@wordpress/scripts': 23.7.2(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2) + '@wordpress/scripts': 23.7.2(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2) '@wordpress/url': 3.48.0 chalk: 4.1.2 expect-puppeteer: 4.4.0 @@ -23172,7 +23358,7 @@ packages: dependencies: '@babel/runtime': 7.23.6 - /@wordpress/eslint-plugin@12.9.0(@babel/core@7.23.6)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2)(wp-prettier@2.6.2): + /@wordpress/eslint-plugin@12.9.0(@babel/core@7.23.6)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2)(wp-prettier@2.6.2): resolution: {integrity: sha512-R6dTvD4uFYeoUJFZNUhm1CSwthC0Pl0RIY057Y9oUvGSqjjm7RqRIwKrMlw3dO0P9KoBGGHUox8NUj6EciRXww==} engines: {node: '>=12', npm: '>=6.9'} peerDependencies: @@ -23195,7 +23381,7 @@ packages: cosmiconfig: 7.1.0 eslint: 8.55.0 eslint-config-prettier: 8.10.0(eslint@8.55.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2) eslint-plugin-jsdoc: 37.9.7(eslint@8.55.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.55.0) @@ -23213,7 +23399,7 @@ packages: - supports-color dev: true - /@wordpress/eslint-plugin@13.10.4(@babel/core@7.23.5)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2)(wp-prettier@2.6.2): + /@wordpress/eslint-plugin@13.10.4(@babel/core@7.23.5)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2)(wp-prettier@2.6.2): resolution: {integrity: sha512-h+vZyDmcu3UIjJT+fyS3/tUllBuLfScbwbJRgHjY1Bul28/VZPd/uNhY2HXnSRYkKD9ngP0MmpBuhsm4+ZSbIg==} engines: {node: '>=14', npm: '>=6.14.4'} peerDependencies: @@ -23236,7 +23422,7 @@ packages: cosmiconfig: 7.1.0 eslint: 8.55.0 eslint-config-prettier: 8.10.0(eslint@8.55.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) eslint-plugin-jest: 27.6.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2) eslint-plugin-jsdoc: 39.9.1(eslint@8.55.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.55.0) @@ -24615,7 +24801,7 @@ packages: url-loader: 4.1.1(webpack@5.89.0) webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) webpack-bundle-analyzer: 4.7.0 - webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack@5.89.0) + webpack-cli: 4.10.0(webpack@5.91.0) webpack-livereload-plugin: 3.0.2(webpack@5.89.0) transitivePeerDependencies: - '@babel/core' @@ -24644,7 +24830,7 @@ packages: - webpack-dev-server dev: true - /@wordpress/scripts@23.7.2(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2): + /@wordpress/scripts@23.7.2(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2): resolution: {integrity: sha512-/VyDK5IeiJyB3abGsj/NbNgnj6fA9AkITCGQkelZX/IbNbGekR1+ocEr2qD2aigHuXVF10Be7W8yRBAXdHy/VQ==} engines: {node: '>=12.13', npm: '>=6.9'} hasBin: true @@ -24653,12 +24839,12 @@ packages: react-dom: ^17.0.0 dependencies: '@babel/core': 7.23.6 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.10.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.10.0)(webpack-dev-server@4.15.1)(webpack@5.91.0) '@svgr/webpack': 6.5.1 '@wordpress/babel-preset-default': 6.17.0 '@wordpress/browserslist-config': 4.1.3 - '@wordpress/dependency-extraction-webpack-plugin': 3.7.0(webpack@5.89.0) - '@wordpress/eslint-plugin': 12.9.0(@babel/core@7.23.6)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2)(wp-prettier@2.6.2) + '@wordpress/dependency-extraction-webpack-plugin': 3.7.0(webpack@5.91.0) + '@wordpress/eslint-plugin': 12.9.0(@babel/core@7.23.6)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2)(wp-prettier@2.6.2) '@wordpress/jest-preset-default': 8.5.2(@babel/core@7.23.6)(jest@27.5.1)(react-dom@17.0.2)(react@17.0.2) '@wordpress/npm-package-json-lint-config': 4.32.0(npm-package-json-lint@5.4.2) '@wordpress/postcss-plugins-preset': 3.10.0(postcss@8.4.32) @@ -24666,14 +24852,14 @@ packages: '@wordpress/stylelint-config': 20.0.3(postcss@8.4.32)(stylelint@14.16.1) adm-zip: 0.5.10 babel-jest: 27.5.1(@babel/core@7.23.6) - babel-loader: 8.3.0(@babel/core@7.23.6)(webpack@5.89.0) + babel-loader: 8.3.0(@babel/core@7.23.6)(webpack@5.91.0) browserslist: 4.19.3 chalk: 4.1.2 check-node-version: 4.2.1 - clean-webpack-plugin: 3.0.0(webpack@5.89.0) - copy-webpack-plugin: 10.2.4(webpack@5.89.0) + clean-webpack-plugin: 3.0.0(webpack@5.91.0) + copy-webpack-plugin: 10.2.4(webpack@5.91.0) cross-spawn: 5.1.0 - css-loader: 6.8.1(webpack@5.89.0) + css-loader: 6.8.1(webpack@5.91.0) cssnano: 5.1.12(postcss@8.4.32) cwd: 0.10.0 dir-glob: 3.0.1 @@ -24686,12 +24872,12 @@ packages: jest-environment-node: 27.5.1 markdownlint-cli: 0.31.1 merge-deep: 3.0.3 - mini-css-extract-plugin: 2.7.6(webpack@5.89.0) + mini-css-extract-plugin: 2.7.6(webpack@5.91.0) minimist: 1.2.8 npm-package-json-lint: 5.4.2 npm-packlist: 3.0.0 postcss: 8.4.32 - postcss-loader: 6.2.1(postcss@8.4.32)(webpack@5.89.0) + postcss-loader: 6.2.1(postcss@8.4.32)(webpack@5.91.0) prettier: /wp-prettier@2.6.2 puppeteer-core: 13.7.0 react: 17.0.2 @@ -24700,15 +24886,15 @@ packages: read-pkg-up: 7.0.1 resolve-bin: 0.4.3 sass: 1.69.5 - sass-loader: 12.6.0(sass@1.69.5)(webpack@5.89.0) - source-map-loader: 3.0.2(webpack@5.89.0) + sass-loader: 12.6.0(sass@1.69.5)(webpack@5.91.0) + source-map-loader: 3.0.2(webpack@5.91.0) stylelint: 14.16.1 - terser-webpack-plugin: 5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.89.0) - url-loader: 4.1.1(webpack@5.89.0) - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + terser-webpack-plugin: 5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.91.0) + url-loader: 4.1.1(webpack@5.91.0) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) webpack-bundle-analyzer: 4.7.0 - webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) - webpack-dev-server: 4.15.1(webpack-cli@4.10.0)(webpack@5.89.0) + webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.91.0) + webpack-dev-server: 4.15.1(webpack-cli@4.10.0)(webpack@5.91.0) transitivePeerDependencies: - '@swc/core' - '@types/webpack' @@ -24737,7 +24923,7 @@ packages: - webpack-plugin-serve dev: true - /@wordpress/scripts@24.6.0(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2): + /@wordpress/scripts@24.6.0(@swc/core@1.3.100)(esbuild@0.18.20)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(react-dom@17.0.2)(react@17.0.2)(typescript@5.3.2): resolution: {integrity: sha512-IbJkihQsjaZz03qyTPcjRF2FWiVVcCm90eL/QutO9cSmZbqfAT1hwNSZIEakiy7GK553k4JirV0eDuMfi4IcWA==} engines: {node: '>=14', npm: '>=6.14.4'} hasBin: true @@ -24746,12 +24932,12 @@ packages: react-dom: ^17.0.0 dependencies: '@babel/core': 7.23.5 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.10.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.10.0)(webpack-dev-server@4.15.1)(webpack@5.91.0) '@svgr/webpack': 6.5.1 '@wordpress/babel-preset-default': 7.28.0 '@wordpress/browserslist-config': 5.21.0 - '@wordpress/dependency-extraction-webpack-plugin': 4.28.0(webpack@5.89.0) - '@wordpress/eslint-plugin': 13.10.4(@babel/core@7.23.5)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2)(wp-prettier@2.6.2) + '@wordpress/dependency-extraction-webpack-plugin': 4.28.0(webpack@5.91.0) + '@wordpress/eslint-plugin': 13.10.4(@babel/core@7.23.5)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0)(jest@27.5.1)(typescript@5.3.2)(wp-prettier@2.6.2) '@wordpress/jest-preset-default': 10.9.0(@babel/core@7.23.5)(jest@27.5.1) '@wordpress/npm-package-json-lint-config': 4.32.0(npm-package-json-lint@5.4.2) '@wordpress/postcss-plugins-preset': 4.31.0(postcss@8.4.32) @@ -24759,14 +24945,14 @@ packages: '@wordpress/stylelint-config': 21.36.0(postcss@8.4.32)(stylelint@14.16.1) adm-zip: 0.5.10 babel-jest: 27.5.1(@babel/core@7.23.5) - babel-loader: 8.3.0(@babel/core@7.23.5)(webpack@5.89.0) + babel-loader: 8.3.0(@babel/core@7.23.5)(webpack@5.91.0) browserslist: 4.19.3 chalk: 4.1.2 check-node-version: 4.2.1 - clean-webpack-plugin: 3.0.0(webpack@5.89.0) - copy-webpack-plugin: 10.2.4(webpack@5.89.0) + clean-webpack-plugin: 3.0.0(webpack@5.91.0) + copy-webpack-plugin: 10.2.4(webpack@5.91.0) cross-spawn: 5.1.0 - css-loader: 6.8.1(webpack@5.89.0) + css-loader: 6.8.1(webpack@5.91.0) cssnano: 5.1.12(postcss@8.4.32) cwd: 0.10.0 dir-glob: 3.0.1 @@ -24779,12 +24965,12 @@ packages: jest-environment-node: 27.5.1 markdownlint-cli: 0.31.1 merge-deep: 3.0.3 - mini-css-extract-plugin: 2.7.6(webpack@5.89.0) + mini-css-extract-plugin: 2.7.6(webpack@5.91.0) minimist: 1.2.8 npm-package-json-lint: 5.4.2 npm-packlist: 3.0.0 postcss: 8.4.32 - postcss-loader: 6.2.1(postcss@8.4.32)(webpack@5.89.0) + postcss-loader: 6.2.1(postcss@8.4.32)(webpack@5.91.0) prettier: /wp-prettier@2.6.2 puppeteer-core: 13.7.0 react: 17.0.2 @@ -24793,15 +24979,15 @@ packages: read-pkg-up: 7.0.1 resolve-bin: 0.4.3 sass: 1.69.5 - sass-loader: 12.6.0(sass@1.69.5)(webpack@5.89.0) - source-map-loader: 3.0.2(webpack@5.89.0) + sass-loader: 12.6.0(sass@1.69.5)(webpack@5.91.0) + source-map-loader: 3.0.2(webpack@5.91.0) stylelint: 14.16.1 - terser-webpack-plugin: 5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.89.0) - url-loader: 4.1.1(webpack@5.89.0) - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + terser-webpack-plugin: 5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.91.0) + url-loader: 4.1.1(webpack@5.91.0) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) webpack-bundle-analyzer: 4.7.0 - webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) - webpack-dev-server: 4.15.1(webpack-cli@4.10.0)(webpack@5.89.0) + webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.91.0) + webpack-dev-server: 4.15.1(webpack-cli@4.10.0)(webpack@5.91.0) transitivePeerDependencies: - '@swc/core' - '@types/webpack' @@ -26241,9 +26427,9 @@ packages: eslint: '>= 4.12.1' dependencies: '@babel/code-frame': 7.23.5 - '@babel/parser': 7.23.6 - '@babel/traverse': 7.23.6 - '@babel/types': 7.23.6 + '@babel/parser': 7.23.5 + '@babel/traverse': 7.23.5 + '@babel/types': 7.23.5 eslint: 7.32.0 eslint-visitor-keys: 1.3.0 resolve: 1.22.8 @@ -26479,7 +26665,7 @@ packages: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.89.0(webpack-cli@4.10.0) dev: true /babel-loader@8.3.0(@babel/core@7.23.5)(webpack@4.47.0): @@ -26512,6 +26698,21 @@ packages: webpack: 5.89.0(webpack-cli@4.10.0) dev: true + /babel-loader@8.3.0(@babel/core@7.23.5)(webpack@5.91.0): + resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} + engines: {node: '>= 8.9'} + peerDependencies: + '@babel/core': ^7.0.0 + webpack: '>=2' + dependencies: + '@babel/core': 7.23.5 + find-cache-dir: 3.3.2 + loader-utils: 2.0.4 + make-dir: 3.1.0 + schema-utils: 2.7.1 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + dev: true + /babel-loader@8.3.0(@babel/core@7.23.6)(webpack@4.47.0): resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} engines: {node: '>= 8.9'} @@ -26542,7 +26743,22 @@ packages: webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) dev: true - /babel-loader@9.1.3(@babel/core@7.23.5)(webpack@5.89.0): + /babel-loader@8.3.0(@babel/core@7.23.6)(webpack@5.91.0): + resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} + engines: {node: '>= 8.9'} + peerDependencies: + '@babel/core': ^7.0.0 + webpack: '>=2' + dependencies: + '@babel/core': 7.23.6 + find-cache-dir: 3.3.2 + loader-utils: 2.0.4 + make-dir: 3.1.0 + schema-utils: 2.7.1 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + dev: true + + /babel-loader@9.1.3(@babel/core@7.23.5)(webpack@5.91.0): resolution: {integrity: sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -26552,7 +26768,7 @@ packages: '@babel/core': 7.23.5 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /babel-messages@6.23.0: @@ -27711,13 +27927,6 @@ packages: semver: 7.5.4 dev: true - /bundle-name@3.0.0: - resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} - engines: {node: '>=12'} - dependencies: - run-applescript: 5.0.0 - dev: true - /bytes@1.0.0: resolution: {integrity: sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==} dev: true @@ -28355,13 +28564,13 @@ packages: inherits: 2.0.4 safe-buffer: 5.2.1 - /circular-dependency-plugin@5.2.2(webpack@5.88.2): + /circular-dependency-plugin@5.2.2(webpack@5.91.0): resolution: {integrity: sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==} engines: {node: '>=6.0.0'} peerDependencies: webpack: '>=4.0.1' dependencies: - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /cjs-module-lexer@0.6.0: @@ -28429,6 +28638,17 @@ packages: webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) dev: true + /clean-webpack-plugin@3.0.0(webpack@5.91.0): + resolution: {integrity: sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==} + engines: {node: '>=8.9.0'} + peerDependencies: + webpack: '*' + dependencies: + '@types/webpack': 4.41.38 + del: 4.1.1 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + dev: true + /cli-boxes@1.0.0: resolution: {integrity: sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg==} engines: {node: '>=0.10.0'} @@ -29080,10 +29300,25 @@ packages: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.1 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.89.0(webpack-cli@4.10.0) dev: true - /copy-webpack-plugin@11.0.0(webpack@5.88.2): + /copy-webpack-plugin@10.2.4(webpack@5.91.0): + resolution: {integrity: sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==} + engines: {node: '>= 12.20.0'} + peerDependencies: + webpack: ^5.1.0 + dependencies: + fast-glob: 3.3.2 + glob-parent: 6.0.2 + globby: 12.2.0 + normalize-path: 3.0.0 + schema-utils: 4.2.0 + serialize-javascript: 6.0.1 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + dev: true + + /copy-webpack-plugin@11.0.0(webpack@5.91.0): resolution: {integrity: sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -29095,7 +29330,7 @@ packages: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.1 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /copy-webpack-plugin@9.1.0(webpack@5.89.0): @@ -29472,7 +29707,7 @@ packages: semver: 6.3.1 webpack: 5.89.0(webpack-cli@3.3.12) - /css-loader@5.2.7(webpack@5.89.0): + /css-loader@5.2.7(webpack@5.91.0): resolution: {integrity: sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -29488,10 +29723,10 @@ packages: postcss-value-parser: 4.2.0 schema-utils: 3.3.0 semver: 7.5.4 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) dev: true - /css-loader@6.8.1(webpack@5.88.2): + /css-loader@6.8.1(webpack@5.89.0): resolution: {integrity: sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==} engines: {node: '>= 12.13.0'} peerDependencies: @@ -29505,10 +29740,10 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.32) postcss-value-parser: 4.2.0 semver: 7.5.4 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.89.0(webpack-cli@4.10.0) dev: true - /css-loader@6.8.1(webpack@5.89.0): + /css-loader@6.8.1(webpack@5.91.0): resolution: {integrity: sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==} engines: {node: '>= 12.13.0'} peerDependencies: @@ -29522,7 +29757,7 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.32) postcss-value-parser: 4.2.0 semver: 7.5.4 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /css-select-base-adapter@0.1.1: @@ -29884,8 +30119,8 @@ packages: whatwg-mimetype: 2.3.0 whatwg-url: 8.7.0 - /dataloader@2.1.0: - resolution: {integrity: sha512-qTcEYLen3r7ojZNgVUaRggOI+KM7jrKxXeSHhogh/TWxYMeONEMqY+hmkobiYQozsGIyg9OYVzO4ZIfoB4I0pQ==} + /dataloader@2.2.2: + resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==} dev: false /date-fns@2.30.0: @@ -29935,7 +30170,7 @@ packages: supports-color: optional: true dependencies: - ms: 2.1.1 + ms: 2.1.3 supports-color: 6.0.0 dev: true @@ -30159,16 +30394,6 @@ packages: untildify: 4.0.0 dev: true - /default-browser@4.0.0: - resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} - engines: {node: '>=14.16'} - dependencies: - bundle-name: 3.0.0 - default-browser-id: 3.0.0 - execa: 7.2.0 - titleize: 3.0.0 - dev: true - /default-gateway@6.0.3: resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} engines: {node: '>= 10'} @@ -30203,11 +30428,6 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} - /define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - dev: true - /define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -30895,6 +31115,14 @@ packages: graceful-fs: 4.2.11 tapable: 2.2.1 + /enhanced-resolve@5.16.0: + resolution: {integrity: sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + dev: true + /enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -31318,9 +31546,9 @@ packages: transitivePeerDependencies: - supports-color - /eslint-import-resolver-typescript@3.2.4(eslint-plugin-import@2.28.1)(eslint@8.55.0): - resolution: {integrity: sha512-XmB2RZq534N3cZajuyMb8c2TJCkCHtU7gUHZg2iJaULIgfIclfQoih08C4/4RmdKZgymAkfHTo4sdmljC6/5qA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-webpack@0.13.2)(eslint-plugin-import@2.28.1)(eslint@8.55.0): + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' eslint-plugin-import: '*' @@ -31328,13 +31556,16 @@ packages: debug: 4.3.4(supports-color@9.4.0) enhanced-resolve: 5.15.0 eslint: 8.55.0 - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) - get-tsconfig: /@unts/get-tsconfig@4.1.1 - globby: 13.2.2 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) + fast-glob: 3.3.2 + get-tsconfig: 4.7.2 is-core-module: 2.13.1 is-glob: 4.0.3 - synckit: 0.7.3 transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack - supports-color dev: true @@ -31360,7 +31591,7 @@ packages: - eslint-import-resolver-webpack - supports-color - /eslint-import-resolver-webpack@0.13.2(eslint-plugin-import@2.28.1)(webpack@5.88.2): + /eslint-import-resolver-webpack@0.13.2(eslint-plugin-import@2.28.1)(webpack@5.91.0): resolution: {integrity: sha512-XodIPyg1OgE2h5BDErz3WJoK7lawxKTJNhgPNafRST6csC/MZC+L5P6kKqsZGRInpbgc02s/WZMrb4uGJzcuRg==} engines: {node: '>= 6'} peerDependencies: @@ -31370,7 +31601,7 @@ packages: array-find: 1.0.0 debug: 3.2.7 enhanced-resolve: 0.9.1 - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) find-root: 1.1.0 has: 1.0.4 interpret: 1.4.0 @@ -31379,7 +31610,7 @@ packages: lodash: 4.17.21 resolve: 1.22.8 semver: 5.7.2 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) transitivePeerDependencies: - supports-color dev: true @@ -31436,7 +31667,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -31461,13 +31692,13 @@ packages: debug: 3.2.7 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.2.4(eslint-plugin-import@2.28.1)(eslint@8.55.0) - eslint-import-resolver-webpack: 0.13.2(eslint-plugin-import@2.28.1)(webpack@5.88.2) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-webpack@0.13.2)(eslint-plugin-import@2.28.1)(eslint@8.55.0) + eslint-import-resolver-webpack: 0.13.2(eslint-plugin-import@2.28.1)(webpack@5.91.0) transitivePeerDependencies: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -31492,8 +31723,8 @@ packages: debug: 3.2.7 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.2.4(eslint-plugin-import@2.28.1)(eslint@8.55.0) - eslint-import-resolver-webpack: 0.13.2(eslint-plugin-import@2.28.1)(webpack@5.88.2) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-webpack@0.13.2)(eslint-plugin-import@2.28.1)(eslint@8.55.0) + eslint-import-resolver-webpack: 0.13.2(eslint-plugin-import@2.28.1)(webpack@5.91.0) transitivePeerDependencies: - supports-color dev: true @@ -31528,7 +31759,7 @@ packages: transitivePeerDependencies: - supports-color - /eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0): + /eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0): resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==} engines: {node: '>=4'} peerDependencies: @@ -31547,7 +31778,7 @@ packages: doctrine: 2.1.0 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.56.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) has: 1.0.4 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -31598,7 +31829,7 @@ packages: - supports-color dev: true - /eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0): + /eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0): resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} engines: {node: '>=4'} peerDependencies: @@ -31617,7 +31848,7 @@ packages: doctrine: 2.1.0 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.2.4)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.13.2)(eslint@8.55.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -31939,8 +32170,8 @@ packages: - supports-color dev: true - /eslint-plugin-playwright@0.15.3(eslint@8.55.0): - resolution: {integrity: sha512-LQMW5y0DLK5Fnpya7JR1oAYL2/7Y9wDiYw6VZqlKqcRGSgjbVKNqxraphk7ra1U3Bb5EK444xMgUlQPbMg2M1g==} + /eslint-plugin-playwright@0.22.1(eslint-plugin-jest@23.20.0)(eslint@8.55.0): + resolution: {integrity: sha512-xUQ9mJH+CjifLG6vMowl3r49G/8JvW4G10IqHjc1WO44fffdhLZF/i4Def+U3y6LqUEBp0JAMnWUhEck7ksqrw==} peerDependencies: eslint: '>=7' eslint-plugin-jest: '>=25' @@ -31949,19 +32180,21 @@ packages: optional: true dependencies: eslint: 8.55.0 + eslint-plugin-jest: 23.20.0(eslint@8.55.0)(typescript@5.3.3) + globals: 13.24.0 dev: true - /eslint-plugin-playwright@0.22.1(eslint-plugin-jest@23.20.0)(eslint@8.55.0): - resolution: {integrity: sha512-xUQ9mJH+CjifLG6vMowl3r49G/8JvW4G10IqHjc1WO44fffdhLZF/i4Def+U3y6LqUEBp0JAMnWUhEck7ksqrw==} + /eslint-plugin-playwright@1.6.0(eslint@8.55.0): + resolution: {integrity: sha512-tI1E/EDbHT4Fx5KvukUG3RTIT0gk44gvTP8bNwxLCFsUXVM98ZJG5zWU6Om5JOzH9FrmN4AhMu/UKyEsu0ZoDA==} + engines: {node: '>=16.6.0'} peerDependencies: - eslint: '>=7' + eslint: '>=8.40.0' eslint-plugin-jest: '>=25' peerDependenciesMeta: eslint-plugin-jest: optional: true dependencies: eslint: 8.55.0 - eslint-plugin-jest: 23.20.0(eslint@8.55.0)(typescript@5.3.3) globals: 13.24.0 dev: true @@ -32933,8 +33166,8 @@ packages: resolution: {integrity: sha512-c/cMBGA5mH3OYjaXedtLIM3hQjv+KuZuiD2QEH5GofNOZeQVDIYIN7Okc2AW1KPhk44g5PTZnXp8t2lOMl8qhQ==} dev: false - /fast-xml-parser@4.2.4: - resolution: {integrity: sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==} + /fast-xml-parser@4.2.5: + resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} hasBin: true dependencies: strnum: 1.0.5 @@ -33498,7 +33731,7 @@ packages: webpack: 4.47.0(webpack-cli@3.3.12) dev: true - /fork-ts-checker-webpack-plugin@6.5.3(eslint@8.55.0)(typescript@5.3.3)(webpack@5.89.0): + /fork-ts-checker-webpack-plugin@6.5.3(eslint@8.55.0)(typescript@5.3.3)(webpack@5.91.0): resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} engines: {node: '>=10', yarn: '>=1.0.0'} peerDependencies: @@ -33527,10 +33760,10 @@ packages: semver: 7.5.4 tapable: 1.1.3 typescript: 5.3.3 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) dev: true - /fork-ts-checker-webpack-plugin@8.0.0(typescript@5.3.2)(webpack@5.89.0): + /fork-ts-checker-webpack-plugin@8.0.0(typescript@5.3.2)(webpack@5.91.0): resolution: {integrity: sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==} engines: {node: '>=12.13.0', yarn: '>=1.0.0'} peerDependencies: @@ -33550,7 +33783,7 @@ packages: semver: 7.5.4 tapable: 2.2.1 typescript: 5.3.2 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /fork-ts-checker-webpack-plugin@8.0.0(typescript@5.3.3)(webpack@5.89.0): @@ -33708,10 +33941,6 @@ packages: readable-stream: 2.3.8 dev: true - /fromentries@1.3.2: - resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} - dev: false - /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} dev: true @@ -34154,7 +34383,7 @@ packages: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.0.4 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 dev: true @@ -35191,7 +35420,7 @@ packages: webpack: 4.47.0(webpack-cli@3.3.12) dev: true - /html-webpack-plugin@5.5.4(webpack@5.89.0): + /html-webpack-plugin@5.5.4(webpack@5.91.0): resolution: {integrity: sha512-3wNSaVVxdxcu0jd4FpQFoICdqgxs4zIQQvj+2yQKFfBOnLETQ6X5CDWdeasuGlSsooFlMkEioWDTqBv1wvw5Iw==} engines: {node: '>=10.13.0'} peerDependencies: @@ -35202,7 +35431,7 @@ packages: lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.2.1 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) dev: true /htmlparser2@3.10.1: @@ -36006,12 +36235,6 @@ packages: engines: {node: '>=8'} hasBin: true - /is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - dev: true - /is-dom@1.1.0: resolution: {integrity: sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ==} dependencies: @@ -36103,14 +36326,6 @@ packages: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} dev: true - /is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - dependencies: - is-docker: 3.0.0 - dev: true - /is-installed-globally@0.1.0: resolution: {integrity: sha512-ERNhMg+i/XgDwPIPF3u24qpajVreaiSuvpb1Uu0jugw7KKcxGyCX8cgp8P5fwTmAuXku6beDHHECdKArjlg7tw==} engines: {node: '>=4'} @@ -37942,7 +38157,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 20.10.4 + '@types/node': 16.18.68 /jest-mock@29.7.0: resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} @@ -40260,11 +40475,6 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - /lru-cache@9.1.2: - resolution: {integrity: sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==} - engines: {node: 14 || >=16.14} - dev: false - /lru@3.1.0: resolution: {integrity: sha512-5OUtoiVIGU4VXBOshidmtOsvBIvcQR6FD/RzWSvaeHyxCGB+PCUCu+52lqMfdc0h/2CLvHhZS4TwUmMQrrMbBQ==} engines: {node: '>= 0.4.0'} @@ -41222,24 +41432,24 @@ packages: webpack-sources: 1.4.3 dev: true - /mini-css-extract-plugin@2.7.6(webpack@5.88.2): + /mini-css-extract-plugin@2.7.6(webpack@5.89.0): resolution: {integrity: sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 dependencies: schema-utils: 4.2.0 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) - dev: true + webpack: 5.89.0(webpack-cli@3.3.12) - /mini-css-extract-plugin@2.7.6(webpack@5.89.0): + /mini-css-extract-plugin@2.7.6(webpack@5.91.0): resolution: {integrity: sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 dependencies: schema-utils: 4.2.0 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + dev: true /minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -42416,21 +42626,20 @@ packages: - typescript dev: true - /octokit@2.1.0: - resolution: {integrity: sha512-Pxi6uKTjBRZWgAwsw1NgHdRlL+QASCN35OYS7X79o7PtBME0CLXEroZmPtEwlWZbPTP+iDbEy2wCbSOgm0uGIQ==} - engines: {node: '>= 14'} + /octokit@3.1.2: + resolution: {integrity: sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==} + engines: {node: '>= 18'} dependencies: - '@octokit/app': 13.1.8 - '@octokit/core': 4.2.4 - '@octokit/oauth-app': 4.2.4 - '@octokit/plugin-paginate-rest': 6.1.2(@octokit/core@4.2.4) - '@octokit/plugin-rest-endpoint-methods': 7.2.3(@octokit/core@4.2.4) - '@octokit/plugin-retry': 4.1.6(@octokit/core@4.2.4) - '@octokit/plugin-throttling': 5.2.3(@octokit/core@4.2.4) - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.2 - transitivePeerDependencies: - - encoding + '@octokit/app': 14.1.0 + '@octokit/core': 5.2.0 + '@octokit/oauth-app': 6.1.0 + '@octokit/plugin-paginate-graphql': 4.0.1(@octokit/core@5.2.0) + '@octokit/plugin-paginate-rest': 9.2.1(@octokit/core@5.2.0) + '@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.2.0) + '@octokit/plugin-retry': 6.0.1(@octokit/core@5.2.0) + '@octokit/plugin-throttling': 8.2.0(@octokit/core@5.2.0) + '@octokit/request-error': 5.1.0 + '@octokit/types': 12.6.0 dev: false /octonode@0.10.2: @@ -42504,16 +42713,6 @@ packages: is-docker: 2.2.1 is-wsl: 2.2.0 - /open@9.1.0: - resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} - engines: {node: '>=14.16'} - dependencies: - default-browser: 4.0.0 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - is-wsl: 2.2.0 - dev: true - /opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -43584,7 +43783,7 @@ packages: webpack: 4.47.0(webpack-cli@3.3.12) dev: true - /postcss-loader@4.3.0(postcss@8.4.32)(webpack@5.88.2): + /postcss-loader@4.3.0(postcss@8.4.32)(webpack@5.89.0): resolution: {integrity: sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -43597,10 +43796,9 @@ packages: postcss: 8.4.32 schema-utils: 3.3.0 semver: 7.5.4 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) - dev: true + webpack: 5.89.0(webpack-cli@4.10.0) - /postcss-loader@4.3.0(postcss@8.4.32)(webpack@5.89.0): + /postcss-loader@4.3.0(postcss@8.4.32)(webpack@5.91.0): resolution: {integrity: sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -43613,7 +43811,8 @@ packages: postcss: 8.4.32 schema-utils: 3.3.0 semver: 7.5.4 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + dev: true /postcss-loader@6.2.1(postcss@8.4.32)(webpack@5.89.0): resolution: {integrity: sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==} @@ -43629,6 +43828,20 @@ packages: webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) dev: true + /postcss-loader@6.2.1(postcss@8.4.32)(webpack@5.91.0): + resolution: {integrity: sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==} + engines: {node: '>= 12.13.0'} + peerDependencies: + postcss: ^7.0.0 || ^8.0.1 + webpack: ^5.0.0 + dependencies: + cosmiconfig: 7.1.0 + klona: 2.0.6 + postcss: 8.4.32 + semver: 7.5.4 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + dev: true + /postcss-media-query-parser@0.2.3: resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} dev: true @@ -44412,14 +44625,14 @@ packages: engines: {node: '>= 0.6.0'} dev: true - /progress-bar-webpack-plugin@2.1.0(webpack@5.88.2): + /progress-bar-webpack-plugin@2.1.0(webpack@5.91.0): resolution: {integrity: sha512-UtlZbnxpYk1wufEWfhIjRn2U52zlY38uvnzFhs8rRxJxC1hSqw88JNR2Mbpqq9Kix8L1nGb3uQ+/1BiUWbigAg==} peerDependencies: webpack: ^1.3.0 || ^2 || ^3 || ^4 || ^5 dependencies: chalk: 3.0.0 progress: 2.0.3 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /progress-event@1.0.0: @@ -45220,7 +45433,7 @@ packages: - bufferutil - utf-8-validate - /react-docgen-typescript-plugin@1.0.5(typescript@5.3.2)(webpack@5.88.2): + /react-docgen-typescript-plugin@1.0.5(typescript@5.3.2)(webpack@5.91.0): resolution: {integrity: sha512-Ds6s2ioyIlH45XSfEVMNwRcDkzuff3xQCPxDFOzTc8GEshy+hksas8RYlmV4JEQREI+OGEGybhMCJk3vFbQZNQ==} peerDependencies: typescript: '>= 4.x' @@ -45234,7 +45447,7 @@ packages: react-docgen-typescript: 2.2.2(typescript@5.3.2) tslib: 2.6.2 typescript: 5.3.2 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) transitivePeerDependencies: - supports-color dev: true @@ -46863,7 +47076,6 @@ packages: hasBin: true dependencies: glob: 10.3.10 - dev: true /ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} @@ -46918,13 +47130,6 @@ packages: strip-json-comments: 3.1.1 dev: true - /run-applescript@5.0.0: - resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} - engines: {node: '>=12'} - dependencies: - execa: 5.1.1 - dev: true - /run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -47086,7 +47291,7 @@ packages: transitivePeerDependencies: - supports-color - /sass-loader@10.5.0(sass@1.69.5)(webpack@5.88.2): + /sass-loader@10.5.0(sass@1.69.5)(webpack@5.89.0): resolution: {integrity: sha512-VsU71W7VR6SChMJZUqtrfLeMSA8ns7QTHbnA7cfevtjb3c392mX93lr0Dmr4uU1ch5uIbEmfmHjdrDYcXXkQ7w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -47108,10 +47313,9 @@ packages: sass: 1.69.5 schema-utils: 3.3.0 semver: 7.5.4 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) - dev: true + webpack: 5.89.0(webpack-cli@4.10.0) - /sass-loader@10.5.0(sass@1.69.5)(webpack@5.89.0): + /sass-loader@10.5.0(sass@1.69.5)(webpack@5.91.0): resolution: {integrity: sha512-VsU71W7VR6SChMJZUqtrfLeMSA8ns7QTHbnA7cfevtjb3c392mX93lr0Dmr4uU1ch5uIbEmfmHjdrDYcXXkQ7w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -47133,7 +47337,8 @@ packages: sass: 1.69.5 schema-utils: 3.3.0 semver: 7.5.4 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + dev: true /sass-loader@12.6.0(sass@1.69.5)(webpack@5.89.0): resolution: {integrity: sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==} @@ -47160,6 +47365,31 @@ packages: webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) dev: true + /sass-loader@12.6.0(sass@1.69.5)(webpack@5.91.0): + resolution: {integrity: sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==} + engines: {node: '>= 12.13.0'} + 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 + dependencies: + klona: 2.0.6 + neo-async: 2.6.2 + sass: 1.69.5 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + dev: true + /sass-loader@8.0.2(sass@1.69.5)(webpack@4.47.0): resolution: {integrity: sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==} engines: {node: '>= 8.9.0'} @@ -47841,6 +48071,18 @@ packages: webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) dev: true + /source-map-loader@3.0.2(webpack@5.91.0): + resolution: {integrity: sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + dependencies: + abab: 2.0.6 + iconv-lite: 0.6.3 + source-map-js: 1.0.2 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + dev: true + /source-map-resolve@0.5.3: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} deprecated: See https://github.com/lydell/source-map-resolve#deprecated @@ -48457,7 +48699,7 @@ packages: webpack: 4.47.0(webpack-cli@3.3.12) dev: true - /style-loader@2.0.0(webpack@5.89.0): + /style-loader@2.0.0(webpack@5.91.0): resolution: {integrity: sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -48465,16 +48707,16 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) dev: true - /style-loader@3.3.3(webpack@5.89.0): + /style-loader@3.3.3(webpack@5.91.0): resolution: {integrity: sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 dependencies: - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /style-search@0.1.0: @@ -48910,14 +49152,14 @@ packages: upper-case: 1.1.3 dev: true - /swc-loader@0.2.3(@swc/core@1.3.100)(webpack@5.89.0): + /swc-loader@0.2.3(@swc/core@1.3.100)(webpack@5.91.0): resolution: {integrity: sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==} peerDependencies: '@swc/core': ^1.2.147 webpack: '>=2' dependencies: '@swc/core': 1.3.100 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /symbol-tree@3.2.4: @@ -48937,14 +49179,6 @@ packages: resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==} dev: true - /synckit@0.7.3: - resolution: {integrity: sha512-jNroMv7Juy+mJ/CHW5H6TzsLWpa1qck6sCHbkv8YTur+irSq2PjbvmGnm2gy14BUQ6jF33vyR4DPssHqmqsDQw==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - dependencies: - '@pkgr/utils': 2.4.2 - tslib: 2.6.2 - dev: true - /syncpack@10.9.3: resolution: {integrity: sha512-urdxuqkvO2/4tB1GaZGbCTzOgdi1XJzHjpiG4DTunOMH4oChSg54hczzzybfFQhqUl0ZY8A6LJNziKsf3J6E7g==} engines: {node: '>=16'} @@ -49180,8 +49414,8 @@ packages: - bluebird dev: true - /terser-webpack-plugin@5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.88.2): - resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} + /terser-webpack-plugin@5.3.10(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.91.0): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -49203,11 +49437,11 @@ packages: schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.26.0 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) dev: true - /terser-webpack-plugin@5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.89.0): - resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} + /terser-webpack-plugin@5.3.10(uglify-js@3.17.4)(webpack@5.91.0): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -49223,17 +49457,16 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.20 - '@swc/core': 1.3.100 - esbuild: 0.18.20 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.26.0 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + uglify-js: 3.17.4 + webpack: 5.91.0(uglify-js@3.17.4)(webpack-cli@4.10.0) dev: true - /terser-webpack-plugin@5.3.6(uglify-js@3.17.4)(webpack@5.89.0): - resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} + /terser-webpack-plugin@5.3.10(webpack@5.91.0): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -49253,11 +49486,10 @@ packages: schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.26.0 - uglify-js: 3.17.4 - webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) + webpack: 5.91.0(webpack-cli@3.3.12) dev: true - /terser-webpack-plugin@5.3.6(webpack@5.70.0): + /terser-webpack-plugin@5.3.6(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.91.0): resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -49274,14 +49506,16 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.20 + '@swc/core': 1.3.100 + esbuild: 0.18.20 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.26.0 - webpack: 5.70.0(webpack-cli@3.3.12) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true - /terser-webpack-plugin@5.3.6(webpack@5.89.0): + /terser-webpack-plugin@5.3.6(uglify-js@3.17.4)(webpack@5.89.0): resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -49302,11 +49536,12 @@ packages: schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.26.0 - webpack: 5.89.0(webpack-cli@3.3.12) + uglify-js: 3.17.4 + webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) dev: true - /terser-webpack-plugin@5.3.9(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.88.2): - resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} + /terser-webpack-plugin@5.3.6(webpack@5.70.0): + resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -49322,17 +49557,15 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.20 - '@swc/core': 1.3.100 - esbuild: 0.18.20 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.26.0 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.70.0(webpack-cli@3.3.12) dev: true - /terser-webpack-plugin@5.3.9(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.89.0): - resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} + /terser-webpack-plugin@5.3.6(webpack@5.91.0): + resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -49348,13 +49581,11 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.20 - '@swc/core': 1.3.100 - esbuild: 0.18.20 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.26.0 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + webpack: 5.91.0(webpack-cli@3.3.12) dev: true /terser-webpack-plugin@5.3.9(uglify-js@3.17.4)(webpack@5.89.0): @@ -49550,11 +49781,6 @@ packages: upper-case: 1.1.3 dev: true - /titleize@3.0.0: - resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} - engines: {node: '>=12'} - dev: true - /tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -49828,7 +50054,23 @@ packages: semver: 7.5.4 source-map: 0.7.4 typescript: 5.3.3 - webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) + webpack: 5.89.0(webpack-cli@3.3.12) + dev: true + + /ts-loader@9.5.1(typescript@5.3.3)(webpack@5.91.0): + resolution: {integrity: sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.15.0 + micromatch: 4.0.5 + semver: 7.5.4 + source-map: 0.7.4 + typescript: 5.3.3 + webpack: 5.91.0(uglify-js@3.17.4)(webpack-cli@4.10.0) dev: true /ts-node@10.9.2(@types/node@16.18.68)(typescript@5.3.3): @@ -50133,6 +50375,7 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true /undici@5.28.2: resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==} @@ -50355,8 +50598,8 @@ packages: unist-util-visit-parents: 3.1.1 dev: true - /universal-github-app-jwt@1.1.1: - resolution: {integrity: sha512-G33RTLrIBMFmlDV4u4CBF7dh71eWwykck4XgaxaIVeZKOYZRAAxvcGMRFTUclVY6xoUPQvO4Ne5wKGxYm/Yy9w==} + /universal-github-app-jwt@1.1.2: + resolution: {integrity: sha512-t1iB2FmLFE+yyJY9+3wMx0ejB+MQpEVkH0gQv7dR6FZyltyq+ZZO0uDpbopxhrZ3SLEO4dCEkIujOMldEQ2iOA==} dependencies: '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 @@ -50506,7 +50749,7 @@ packages: loader-utils: 1.4.2 mime: 2.6.0 schema-utils: 1.0.0 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.89.0(webpack-cli@4.10.0) dev: true /url-loader@3.0.0(webpack@4.47.0): @@ -50558,6 +50801,22 @@ packages: webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) dev: true + /url-loader@4.1.1(webpack@5.91.0): + resolution: {integrity: sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==} + engines: {node: '>= 10.13.0'} + peerDependencies: + file-loader: '*' + webpack: ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + file-loader: + optional: true + dependencies: + loader-utils: 2.0.4 + mime-types: 2.1.35 + schema-utils: 3.3.0 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + dev: true + /url-parse-lax@1.0.0: resolution: {integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==} engines: {node: '>=0.10.0'} @@ -51120,6 +51379,14 @@ packages: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + /watchpack@2.4.1: + resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} + engines: {node: '>=10.13.0'} + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + dev: true + /wbuf@1.7.3: resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==} dependencies: @@ -51292,7 +51559,7 @@ packages: webpack-dev-server: 4.15.1(debug@4.3.4)(webpack-cli@4.10.0)(webpack@5.89.0) webpack-merge: 5.10.0 - /webpack-cli@4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.89.0): + /webpack-cli@4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.91.0): resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} engines: {node: '>=10.13.0'} hasBin: true @@ -51313,7 +51580,7 @@ packages: optional: true dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0)(webpack@5.89.0) + '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0)(webpack@5.91.0) '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0) '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0)(webpack-dev-server@4.15.1) colorette: 2.0.20 @@ -51323,13 +51590,13 @@ packages: import-local: 3.1.0 interpret: 2.2.0 rechoir: 0.7.1 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) webpack-bundle-analyzer: 4.7.0 - webpack-dev-server: 4.15.1(webpack-cli@4.10.0)(webpack@5.89.0) + webpack-dev-server: 4.15.1(webpack-cli@4.10.0)(webpack@5.91.0) webpack-merge: 5.10.0 dev: true - /webpack-cli@4.10.0(webpack-bundle-analyzer@4.7.0)(webpack@5.89.0): + /webpack-cli@4.10.0(webpack@5.91.0): resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} engines: {node: '>=10.13.0'} hasBin: true @@ -51350,7 +51617,7 @@ packages: optional: true dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0)(webpack@5.89.0) + '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0)(webpack@5.91.0) '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0) '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0) colorette: 2.0.20 @@ -51360,12 +51627,11 @@ packages: import-local: 3.1.0 interpret: 2.2.0 rechoir: 0.7.1 - webpack: 5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0) - webpack-bundle-analyzer: 4.7.0 + webpack: 5.91.0(uglify-js@3.17.4)(webpack-cli@4.10.0) webpack-merge: 5.10.0 dev: true - /webpack-cli@5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.88.2): + /webpack-cli@5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.91.0): resolution: {integrity: sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==} engines: {node: '>=14.15.0'} hasBin: true @@ -51383,9 +51649,9 @@ packages: optional: true dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.88.2) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.88.2) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.88.2) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.91.0) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.91.0) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.91.0) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.3 @@ -51394,7 +51660,7 @@ packages: import-local: 3.1.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) webpack-bundle-analyzer: 4.7.0 webpack-merge: 5.10.0 dev: true @@ -51413,7 +51679,7 @@ packages: webpack-log: 2.0.0 dev: true - /webpack-dev-middleware@4.3.0(webpack@5.89.0): + /webpack-dev-middleware@4.3.0(webpack@5.91.0): resolution: {integrity: sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w==} engines: {node: '>= v10.23.3'} peerDependencies: @@ -51425,7 +51691,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 3.3.0 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.91.0(webpack-cli@3.3.12) dev: true /webpack-dev-middleware@5.3.3(webpack@5.89.0): @@ -51441,7 +51707,21 @@ packages: schema-utils: 4.2.0 webpack: 5.89.0(webpack-cli@4.10.0) - /webpack-dev-middleware@6.1.1(webpack@5.89.0): + /webpack-dev-middleware@5.3.3(webpack@5.91.0): + resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + dependencies: + colorette: 2.0.20 + memfs: 3.5.3 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.2.0 + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + dev: true + + /webpack-dev-middleware@6.1.1(webpack@5.91.0): resolution: {integrity: sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -51455,7 +51735,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.2.0 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true /webpack-dev-server@4.15.1(debug@4.3.4)(webpack-cli@4.10.0)(webpack@5.89.0): @@ -51509,7 +51789,7 @@ packages: - supports-color - utf-8-validate - /webpack-dev-server@4.15.1(webpack-cli@4.10.0)(webpack@5.89.0): + /webpack-dev-server@4.15.1(webpack-cli@4.10.0)(webpack@5.91.0): resolution: {integrity: sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==} engines: {node: '>= 12.13.0'} hasBin: true @@ -51550,9 +51830,9 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) - webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) - webpack-dev-middleware: 5.3.3(webpack@5.89.0) + webpack: 5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0) + webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.91.0) + webpack-dev-middleware: 5.3.3(webpack@5.91.0) ws: 8.15.0 transitivePeerDependencies: - bufferutil @@ -51630,7 +51910,7 @@ packages: webpack: '>=5.32.0' dependencies: ansis: 1.5.6 - webpack: 5.89.0(webpack-cli@3.3.12) + webpack: 5.89.0(webpack-cli@4.10.0) /webpack-rtl-plugin@2.0.0: resolution: {integrity: sha512-lROgFkiPjapg9tcZ8FiLWeP5pJoG00018aEjLTxSrVldPD1ON+LPlhKPHjb7eE8Bc0+KL23pxcAjWDGOv9+UAw==} @@ -51760,8 +52040,8 @@ packages: - uglify-js dev: true - /webpack@5.88.2(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4): - resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} + /webpack@5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0): + resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -51791,9 +52071,9 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.88.2) + terser-webpack-plugin: 5.3.9(uglify-js@3.17.4)(webpack@5.89.0) watchpack: 2.4.0 - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.88.2) + webpack-cli: 4.10.0(webpack@5.91.0) webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -51801,7 +52081,7 @@ packages: - uglify-js dev: true - /webpack@5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0): + /webpack@5.89.0(webpack-cli@3.3.12): resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} engines: {node: '>=10.13.0'} hasBin: true @@ -51832,17 +52112,16 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.89.0) + terser-webpack-plugin: 5.3.9(webpack@5.89.0) watchpack: 2.4.0 - webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) + webpack-cli: 3.3.12(webpack@5.89.0) webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' - esbuild - uglify-js - dev: true - /webpack@5.89.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4): + /webpack@5.89.0(webpack-cli@4.10.0): resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} engines: {node: '>=10.13.0'} hasBin: true @@ -51859,7 +52138,7 @@ packages: '@webassemblyjs/wasm-parser': 1.11.6 acorn: 8.11.2 acorn-import-assertions: 1.9.0(acorn@8.11.2) - browserslist: 4.22.2 + browserslist: 4.19.3 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 es-module-lexer: 1.4.1 @@ -51873,9 +52152,49 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.89.0) + terser-webpack-plugin: 5.3.9(webpack@5.89.0) watchpack: 2.4.0 - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.88.2) + webpack-cli: 4.10.0(webpack-bundle-analyzer@3.9.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + /webpack@5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@4.10.0): + resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.5 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + acorn: 8.11.2 + acorn-import-assertions: 1.9.0(acorn@8.11.2) + browserslist: 4.22.2 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.16.0 + es-module-lexer: 1.4.1 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.91.0) + watchpack: 2.4.1 + webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.15.1)(webpack@5.91.0) webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -51883,8 +52202,8 @@ packages: - uglify-js dev: true - /webpack@5.89.0(uglify-js@3.17.4)(webpack-cli@4.10.0): - resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} + /webpack@5.91.0(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4): + resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -51895,14 +52214,14 @@ packages: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/wasm-edit': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 acorn: 8.11.2 acorn-import-assertions: 1.9.0(acorn@8.11.2) browserslist: 4.22.2 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.15.0 + enhanced-resolve: 5.16.0 es-module-lexer: 1.4.1 eslint-scope: 5.1.1 events: 3.3.0 @@ -51914,9 +52233,9 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(uglify-js@3.17.4)(webpack@5.89.0) - watchpack: 2.4.0 - webpack-cli: 4.10.0(webpack-bundle-analyzer@4.7.0)(webpack@5.89.0) + terser-webpack-plugin: 5.3.10(@swc/core@1.3.100)(esbuild@0.18.20)(webpack@5.91.0) + watchpack: 2.4.1 + webpack-cli: 5.1.4(webpack-bundle-analyzer@4.7.0)(webpack@5.91.0) webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -51924,8 +52243,8 @@ packages: - uglify-js dev: true - /webpack@5.89.0(webpack-cli@3.3.12): - resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} + /webpack@5.91.0(uglify-js@3.17.4)(webpack-cli@4.10.0): + resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -51936,14 +52255,14 @@ packages: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/wasm-edit': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 acorn: 8.11.2 acorn-import-assertions: 1.9.0(acorn@8.11.2) browserslist: 4.22.2 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.15.0 + enhanced-resolve: 5.16.0 es-module-lexer: 1.4.1 eslint-scope: 5.1.1 events: 3.3.0 @@ -51955,17 +52274,18 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.89.0) - watchpack: 2.4.0 - webpack-cli: 3.3.12(webpack@5.89.0) + terser-webpack-plugin: 5.3.10(uglify-js@3.17.4)(webpack@5.91.0) + watchpack: 2.4.1 + webpack-cli: 4.10.0(webpack@5.91.0) webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' - esbuild - uglify-js + dev: true - /webpack@5.89.0(webpack-cli@4.10.0): - resolution: {integrity: sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==} + /webpack@5.91.0(webpack-cli@3.3.12): + resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -51976,14 +52296,14 @@ packages: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/wasm-edit': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 acorn: 8.11.2 acorn-import-assertions: 1.9.0(acorn@8.11.2) browserslist: 4.22.2 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.15.0 + enhanced-resolve: 5.16.0 es-module-lexer: 1.4.1 eslint-scope: 5.1.1 events: 3.3.0 @@ -51995,14 +52315,15 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.89.0) - watchpack: 2.4.0 - webpack-cli: 4.10.0(webpack-bundle-analyzer@3.9.0)(webpack-dev-server@4.15.1)(webpack@5.89.0) + terser-webpack-plugin: 5.3.10(webpack@5.91.0) + watchpack: 2.4.1 + webpack-cli: 3.3.12(webpack@5.89.0) webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' - esbuild - uglify-js + dev: true /websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} @@ -52932,7 +53253,7 @@ packages: file:plugins/woocommerce-blocks/bin/eslint-plugin-woocommerce: resolution: {directory: plugins/woocommerce-blocks/bin/eslint-plugin-woocommerce, type: directory} name: eslint-plugin-woocommerce - engines: {node: ^16.13.0, npm: ^8.0.0} + engines: {node: ^20.11.1, npm: ^8.0.0} dev: true github.com/Automattic/puppeteer-utils/0f3ec50(react-native@0.73.0): diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 06427a012328c..3ee77186bd0fe 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,7 @@ packages: - 'packages/php/*' - 'plugins/*' - 'plugins/woocommerce/client/legacy' + - 'plugins/woocommerce-blocks/bin/eslint-plugin-woocommerce' - 'tools/monorepo-merge' - 'tools/code-analyzer' - 'tools/compare-perf' diff --git a/tools/code-analyzer/package.json b/tools/code-analyzer/package.json index 7dcd62d2c82f4..7866c6928e773 100644 --- a/tools/code-analyzer/package.json +++ b/tools/code-analyzer/package.json @@ -23,7 +23,7 @@ "commander": "^9.5.0", "dotenv": "^10.0.0", "simple-git": "^3.21.0", - "uuid": "^8.3.2" + "uuid": "^9.0.1" }, "devDependencies": { "@types/jest": "^27.5.2", @@ -38,7 +38,7 @@ "wireit": "0.14.3" }, "engines": { - "node": "^16.14.1", + "node": "^20.11.1", "pnpm": "^8.12.1" }, "config": { diff --git a/tools/code-analyzer/src/lib/__tests__/fixtures/diff.txt b/tools/code-analyzer/src/lib/__tests__/fixtures/diff.txt index b47c9075567ca..a3cdd5176077f 100644 --- a/tools/code-analyzer/src/lib/__tests__/fixtures/diff.txt +++ b/tools/code-analyzer/src/lib/__tests__/fixtures/diff.txt @@ -414,9 +414,9 @@ index 71dd29b060..33e289bf86 100644 +env: + TIME_OVERRIDE: ${{ inputs.timeOverride }} + GIT_COMMITTER_NAME: 'WooCommerce Bot' -+ GIT_COMMITTER_EMAIL: 'no-reply@woo.com' ++ GIT_COMMITTER_EMAIL: 'no-reply@woocommerce.com' + GIT_AUTHOR_NAME: 'WooCommerce Bot' -+ GIT_AUTHOR_EMAIL: 'no-reply@woo.com' ++ GIT_AUTHOR_EMAIL: 'no-reply@woocommerce.com' jobs: maybe-create-next-milestone-and-release-branch: diff --git a/tools/code-analyzer/src/lib/template-changes.ts b/tools/code-analyzer/src/lib/template-changes.ts index badf9cd50cb70..222e706aa203a 100644 --- a/tools/code-analyzer/src/lib/template-changes.ts +++ b/tools/code-analyzer/src/lib/template-changes.ts @@ -37,6 +37,7 @@ export const scanForTemplateChanges = async ( ) }).*`; const versionRegex = new RegExp( matchVersion, 'g' ); + const deletedRegex = new RegExp( '^deleted file mode [0-9]+' ); for ( const p in patches ) { const patch = patches[ p ]; @@ -51,9 +52,16 @@ export const scanForTemplateChanges = async ( for ( const l in lines ) { const line = lines[ l ]; + if ( line.match( deletedRegex ) ) { + code = 'notice'; + message = 'Template deleted'; + break; + } + if ( line.match( versionRegex ) ) { code = 'notice'; message = 'Version bump found'; + break; } if ( repositoryPath ) { diff --git a/tools/compare-perf/config.js b/tools/compare-perf/config.js index cd140ca5a2ae8..a4cc42a3155f3 100644 --- a/tools/compare-perf/config.js +++ b/tools/compare-perf/config.js @@ -1,13 +1,17 @@ +const packageJson = require( '../../package.json' ); +let pnpm_package = 'pnpm'; + +if ( packageJson.engines.pnpm ) { + pnpm_package = `pnpm@${ packageJson.engines.pnpm }`; +} + const config = { gitRepositoryURL: 'https://github.com/woocommerce/woocommerce.git', - setupTestRunner: - 'npm install -g pnpm && pnpm install --filter="@woocommerce/plugin-woocommerce" &> /dev/null && cd plugins/woocommerce && pnpm exec playwright install chromium', - setupCommand: - 'npm install -g pnpm && pnpm install &> /dev/null && pnpm build &> /dev/null', + setupTestRunner: `npm install -g ${ pnpm_package } && pnpm install --filter="@woocommerce/plugin-woocommerce" &> /dev/null && cd plugins/woocommerce && pnpm exec playwright install chromium`, + setupCommand: `npm install -g ${ pnpm_package } && pnpm install &> /dev/null && pnpm build &> /dev/null`, pluginPath: '/plugins/woocommerce', testsPath: '/plugins/woocommerce/tests/metrics/specs', - testCommand: - 'npm install -g pnpm && cd plugins/woocommerce && pnpm test:metrics', + testCommand: `npm install -g ${ pnpm_package } && cd plugins/woocommerce && pnpm test:metrics`, }; module.exports = config; diff --git a/tools/compare-perf/package.json b/tools/compare-perf/package.json index 6d604b6d7b322..54db5c2ebd906 100644 --- a/tools/compare-perf/package.json +++ b/tools/compare-perf/package.json @@ -18,7 +18,7 @@ "simple-git": "^3.21.0" }, "engines": { - "node": "^16.14.1", + "node": "^20.11.1", "pnpm": "^8.12.1" } } diff --git a/tools/monorepo-merge/package.json b/tools/monorepo-merge/package.json index 65aa227e4607d..5a9eb4f845a58 100644 --- a/tools/monorepo-merge/package.json +++ b/tools/monorepo-merge/package.json @@ -62,7 +62,7 @@ } }, "engines": { - "node": "^16.14.1", + "node": "^20.11.1", "pnpm": "^8.12.1" }, "types": "dist/index.d.ts", diff --git a/tools/monorepo-utils/dist/index.js b/tools/monorepo-utils/dist/index.js index fec19b052ce34..c77f26c67f7ca 100644 --- a/tools/monorepo-utils/dist/index.js +++ b/tools/monorepo-utils/dist/index.js @@ -1,2 +1,2 @@ /*! For license information please see index.js.LICENSE.txt */ -(()=>{var __webpack_modules__={4797:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i),Object.defineProperty(e,s,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.issue=t.issueCommand=void 0;const a=n(i(22037)),o=i(54106);function c(e,t,i){const s=new l(e,t,i);process.stdout.write(s.toString()+a.EOL)}t.issueCommand=c,t.issue=function(e,t=""){c(e,{},t)};class l{constructor(e,t,i){e||(e="missing.command"),this.command=e,this.properties=t,this.message=i}toString(){let e="::"+this.command;if(this.properties&&Object.keys(this.properties).length>0){e+=" ";let i=!0;for(const s in this.properties)if(this.properties.hasOwnProperty(s)){const r=this.properties[s];r&&(i?i=!1:e+=",",e+=`${s}=${t=r,o.toCommandValue(t).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/:/g,"%3A").replace(/,/g,"%2C")}`)}}var t;return e+=`::${function(e){return o.toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A")}(this.message)}`,e}}},57995:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i),Object.defineProperty(e,s,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t},a=this&&this.__awaiter||function(e,t,i,s){return new(i||(i=Promise))((function(r,n){function a(e){try{c(s.next(e))}catch(e){n(e)}}function o(e){try{c(s.throw(e))}catch(e){n(e)}}function c(e){var t;e.done?r(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(a,o)}c((s=s.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getIDToken=t.getState=t.saveState=t.group=t.endGroup=t.startGroup=t.info=t.notice=t.warning=t.error=t.debug=t.isDebug=t.setFailed=t.setCommandEcho=t.setOutput=t.getBooleanInput=t.getMultilineInput=t.getInput=t.addPath=t.setSecret=t.exportVariable=t.ExitCode=void 0;const o=i(4797),c=i(8096),l=i(54106),p=n(i(22037)),A=n(i(71017)),u=i(271);var d;function h(e,t){const i=process.env[`INPUT_${e.replace(/ /g,"_").toUpperCase()}`]||"";if(t&&t.required&&!i)throw new Error(`Input required and not supplied: ${e}`);return t&&!1===t.trimWhitespace?i:i.trim()}function m(e,t={}){o.issueCommand("error",l.toCommandProperties(t),e instanceof Error?e.toString():e)}function g(e){o.issue("group",e)}function f(){o.issue("endgroup")}!function(e){e[e.Success=0]="Success",e[e.Failure=1]="Failure"}(d=t.ExitCode||(t.ExitCode={})),t.exportVariable=function(e,t){const i=l.toCommandValue(t);if(process.env[e]=i,process.env.GITHUB_ENV)return c.issueFileCommand("ENV",c.prepareKeyValueMessage(e,t));o.issueCommand("set-env",{name:e},i)},t.setSecret=function(e){o.issueCommand("add-mask",{},e)},t.addPath=function(e){process.env.GITHUB_PATH?c.issueFileCommand("PATH",e):o.issueCommand("add-path",{},e),process.env.PATH=`${e}${A.delimiter}${process.env.PATH}`},t.getInput=h,t.getMultilineInput=function(e,t){const i=h(e,t).split("\n").filter((e=>""!==e));return t&&!1===t.trimWhitespace?i:i.map((e=>e.trim()))},t.getBooleanInput=function(e,t){const i=h(e,t);if(["true","True","TRUE"].includes(i))return!0;if(["false","False","FALSE"].includes(i))return!1;throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${e}\nSupport boolean input list: \`true | True | TRUE | false | False | FALSE\``)},t.setOutput=function(e,t){if(process.env.GITHUB_OUTPUT)return c.issueFileCommand("OUTPUT",c.prepareKeyValueMessage(e,t));process.stdout.write(p.EOL),o.issueCommand("set-output",{name:e},l.toCommandValue(t))},t.setCommandEcho=function(e){o.issue("echo",e?"on":"off")},t.setFailed=function(e){process.exitCode=d.Failure,m(e)},t.isDebug=function(){return"1"===process.env.RUNNER_DEBUG},t.debug=function(e){o.issueCommand("debug",{},e)},t.error=m,t.warning=function(e,t={}){o.issueCommand("warning",l.toCommandProperties(t),e instanceof Error?e.toString():e)},t.notice=function(e,t={}){o.issueCommand("notice",l.toCommandProperties(t),e instanceof Error?e.toString():e)},t.info=function(e){process.stdout.write(e+p.EOL)},t.startGroup=g,t.endGroup=f,t.group=function(e,t){return a(this,void 0,void 0,(function*(){let i;g(e);try{i=yield t()}finally{f()}return i}))},t.saveState=function(e,t){if(process.env.GITHUB_STATE)return c.issueFileCommand("STATE",c.prepareKeyValueMessage(e,t));o.issueCommand("save-state",{name:e},l.toCommandValue(t))},t.getState=function(e){return process.env[`STATE_${e}`]||""},t.getIDToken=function(e){return a(this,void 0,void 0,(function*(){return yield u.OidcClient.getIDToken(e)}))};var E=i(26163);Object.defineProperty(t,"summary",{enumerable:!0,get:function(){return E.summary}});var C=i(26163);Object.defineProperty(t,"markdownSummary",{enumerable:!0,get:function(){return C.markdownSummary}});var y=i(56520);Object.defineProperty(t,"toPosixPath",{enumerable:!0,get:function(){return y.toPosixPath}}),Object.defineProperty(t,"toWin32Path",{enumerable:!0,get:function(){return y.toWin32Path}}),Object.defineProperty(t,"toPlatformPath",{enumerable:!0,get:function(){return y.toPlatformPath}})},8096:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i),Object.defineProperty(e,s,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.prepareKeyValueMessage=t.issueFileCommand=void 0;const a=n(i(57147)),o=n(i(22037)),c=i(68040),l=i(54106);t.issueFileCommand=function(e,t){const i=process.env[`GITHUB_${e}`];if(!i)throw new Error(`Unable to find environment variable for file command ${e}`);if(!a.existsSync(i))throw new Error(`Missing file at path: ${i}`);a.appendFileSync(i,`${l.toCommandValue(t)}${o.EOL}`,{encoding:"utf8"})},t.prepareKeyValueMessage=function(e,t){const i=`ghadelimiter_${c.v4()}`,s=l.toCommandValue(t);if(e.includes(i))throw new Error(`Unexpected input: name should not contain the delimiter "${i}"`);if(s.includes(i))throw new Error(`Unexpected input: value should not contain the delimiter "${i}"`);return`${e}<<${i}${o.EOL}${s}${o.EOL}${i}`}},271:function(e,t,i){"use strict";var s=this&&this.__awaiter||function(e,t,i,s){return new(i||(i=Promise))((function(r,n){function a(e){try{c(s.next(e))}catch(e){n(e)}}function o(e){try{c(s.throw(e))}catch(e){n(e)}}function c(e){var t;e.done?r(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(a,o)}c((s=s.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.OidcClient=void 0;const r=i(48543),n=i(65343),a=i(57995);class o{static createHttpClient(e=!0,t=10){const i={allowRetries:e,maxRetries:t};return new r.HttpClient("actions/oidc-client",[new n.BearerCredentialHandler(o.getRequestToken())],i)}static getRequestToken(){const e=process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;if(!e)throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable");return e}static getIDTokenUrl(){const e=process.env.ACTIONS_ID_TOKEN_REQUEST_URL;if(!e)throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable");return e}static getCall(e){var t;return s(this,void 0,void 0,(function*(){const i=o.createHttpClient(),s=yield i.getJson(e).catch((e=>{throw new Error(`Failed to get ID Token. \n \n Error Code : ${e.statusCode}\n \n Error Message: ${e.message}`)})),r=null===(t=s.result)||void 0===t?void 0:t.value;if(!r)throw new Error("Response json body do not have ID Token field");return r}))}static getIDToken(e){return s(this,void 0,void 0,(function*(){try{let t=o.getIDTokenUrl();e&&(t=`${t}&audience=${encodeURIComponent(e)}`),a.debug(`ID token url is ${t}`);const i=yield o.getCall(t);return a.setSecret(i),i}catch(e){throw new Error(`Error message: ${e.message}`)}}))}}t.OidcClient=o},56520:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i),Object.defineProperty(e,s,{enumerable:!0,get:function(){return t[i]}})}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.toPlatformPath=t.toWin32Path=t.toPosixPath=void 0;const a=n(i(71017));t.toPosixPath=function(e){return e.replace(/[\\]/g,"/")},t.toWin32Path=function(e){return e.replace(/[/]/g,"\\")},t.toPlatformPath=function(e){return e.replace(/[/\\]/g,a.sep)}},26163:function(e,t,i){"use strict";var s=this&&this.__awaiter||function(e,t,i,s){return new(i||(i=Promise))((function(r,n){function a(e){try{c(s.next(e))}catch(e){n(e)}}function o(e){try{c(s.throw(e))}catch(e){n(e)}}function c(e){var t;e.done?r(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(a,o)}c((s=s.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.summary=t.markdownSummary=t.SUMMARY_DOCS_URL=t.SUMMARY_ENV_VAR=void 0;const r=i(22037),n=i(57147),{access:a,appendFile:o,writeFile:c}=n.promises;t.SUMMARY_ENV_VAR="GITHUB_STEP_SUMMARY",t.SUMMARY_DOCS_URL="https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary";const l=new class{constructor(){this._buffer=""}filePath(){return s(this,void 0,void 0,(function*(){if(this._filePath)return this._filePath;const e=process.env[t.SUMMARY_ENV_VAR];if(!e)throw new Error(`Unable to find environment variable for $${t.SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`);try{yield a(e,n.constants.R_OK|n.constants.W_OK)}catch(t){throw new Error(`Unable to access summary file: '${e}'. Check if the file has correct read/write permissions.`)}return this._filePath=e,this._filePath}))}wrap(e,t,i={}){const s=Object.entries(i).map((([e,t])=>` ${e}="${t}"`)).join("");return t?`<${e}${s}>${t}`:`<${e}${s}>`}write(e){return s(this,void 0,void 0,(function*(){const t=!!(null==e?void 0:e.overwrite),i=yield this.filePath(),s=t?c:o;return yield s(i,this._buffer,{encoding:"utf8"}),this.emptyBuffer()}))}clear(){return s(this,void 0,void 0,(function*(){return this.emptyBuffer().write({overwrite:!0})}))}stringify(){return this._buffer}isEmptyBuffer(){return 0===this._buffer.length}emptyBuffer(){return this._buffer="",this}addRaw(e,t=!1){return this._buffer+=e,t?this.addEOL():this}addEOL(){return this.addRaw(r.EOL)}addCodeBlock(e,t){const i=Object.assign({},t&&{lang:t}),s=this.wrap("pre",this.wrap("code",e),i);return this.addRaw(s).addEOL()}addList(e,t=!1){const i=t?"ol":"ul",s=e.map((e=>this.wrap("li",e))).join(""),r=this.wrap(i,s);return this.addRaw(r).addEOL()}addTable(e){const t=e.map((e=>{const t=e.map((e=>{if("string"==typeof e)return this.wrap("td",e);const{header:t,data:i,colspan:s,rowspan:r}=e,n=t?"th":"td",a=Object.assign(Object.assign({},s&&{colspan:s}),r&&{rowspan:r});return this.wrap(n,i,a)})).join("");return this.wrap("tr",t)})).join(""),i=this.wrap("table",t);return this.addRaw(i).addEOL()}addDetails(e,t){const i=this.wrap("details",this.wrap("summary",e)+t);return this.addRaw(i).addEOL()}addImage(e,t,i){const{width:s,height:r}=i||{},n=Object.assign(Object.assign({},s&&{width:s}),r&&{height:r}),a=this.wrap("img",null,Object.assign({src:e,alt:t},n));return this.addRaw(a).addEOL()}addHeading(e,t){const i=`h${t}`,s=["h1","h2","h3","h4","h5","h6"].includes(i)?i:"h1",r=this.wrap(s,e);return this.addRaw(r).addEOL()}addSeparator(){const e=this.wrap("hr",null);return this.addRaw(e).addEOL()}addBreak(){const e=this.wrap("br",null);return this.addRaw(e).addEOL()}addQuote(e,t){const i=Object.assign({},t&&{cite:t}),s=this.wrap("blockquote",e,i);return this.addRaw(s).addEOL()}addLink(e,t){const i=this.wrap("a",e,{href:t});return this.addRaw(i).addEOL()}};t.markdownSummary=l,t.summary=l},54106:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.toCommandProperties=t.toCommandValue=void 0,t.toCommandValue=function(e){return null==e?"":"string"==typeof e||e instanceof String?e:JSON.stringify(e)},t.toCommandProperties=function(e){return Object.keys(e).length?{title:e.title,file:e.file,line:e.startLine,endLine:e.endLine,col:e.startColumn,endColumn:e.endColumn}:{}}},65343:function(e,t){"use strict";var i=this&&this.__awaiter||function(e,t,i,s){return new(i||(i=Promise))((function(r,n){function a(e){try{c(s.next(e))}catch(e){n(e)}}function o(e){try{c(s.throw(e))}catch(e){n(e)}}function c(e){var t;e.done?r(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(a,o)}c((s=s.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.PersonalAccessTokenCredentialHandler=t.BearerCredentialHandler=t.BasicCredentialHandler=void 0,t.BasicCredentialHandler=class{constructor(e,t){this.username=e,this.password=t}prepareRequest(e){if(!e.headers)throw Error("The request has no headers");e.headers.Authorization=`Basic ${Buffer.from(`${this.username}:${this.password}`).toString("base64")}`}canHandleAuthentication(){return!1}handleAuthentication(){return i(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}},t.BearerCredentialHandler=class{constructor(e){this.token=e}prepareRequest(e){if(!e.headers)throw Error("The request has no headers");e.headers.Authorization=`Bearer ${this.token}`}canHandleAuthentication(){return!1}handleAuthentication(){return i(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}},t.PersonalAccessTokenCredentialHandler=class{constructor(e){this.token=e}prepareRequest(e){if(!e.headers)throw Error("The request has no headers");e.headers.Authorization=`Basic ${Buffer.from(`PAT:${this.token}`).toString("base64")}`}canHandleAuthentication(){return!1}handleAuthentication(){return i(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}},48543:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t},a=this&&this.__awaiter||function(e,t,i,s){return new(i||(i=Promise))((function(r,n){function a(e){try{c(s.next(e))}catch(e){n(e)}}function o(e){try{c(s.throw(e))}catch(e){n(e)}}function c(e){var t;e.done?r(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(a,o)}c((s=s.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.HttpClient=t.isHttps=t.HttpClientResponse=t.HttpClientError=t.getProxyUrl=t.MediaTypes=t.Headers=t.HttpCodes=void 0;const o=n(i(13685)),c=n(i(95687)),l=n(i(20359)),p=n(i(42785)),A=i(90083);var u,d,h;!function(e){e[e.OK=200]="OK",e[e.MultipleChoices=300]="MultipleChoices",e[e.MovedPermanently=301]="MovedPermanently",e[e.ResourceMoved=302]="ResourceMoved",e[e.SeeOther=303]="SeeOther",e[e.NotModified=304]="NotModified",e[e.UseProxy=305]="UseProxy",e[e.SwitchProxy=306]="SwitchProxy",e[e.TemporaryRedirect=307]="TemporaryRedirect",e[e.PermanentRedirect=308]="PermanentRedirect",e[e.BadRequest=400]="BadRequest",e[e.Unauthorized=401]="Unauthorized",e[e.PaymentRequired=402]="PaymentRequired",e[e.Forbidden=403]="Forbidden",e[e.NotFound=404]="NotFound",e[e.MethodNotAllowed=405]="MethodNotAllowed",e[e.NotAcceptable=406]="NotAcceptable",e[e.ProxyAuthenticationRequired=407]="ProxyAuthenticationRequired",e[e.RequestTimeout=408]="RequestTimeout",e[e.Conflict=409]="Conflict",e[e.Gone=410]="Gone",e[e.TooManyRequests=429]="TooManyRequests",e[e.InternalServerError=500]="InternalServerError",e[e.NotImplemented=501]="NotImplemented",e[e.BadGateway=502]="BadGateway",e[e.ServiceUnavailable=503]="ServiceUnavailable",e[e.GatewayTimeout=504]="GatewayTimeout"}(u||(t.HttpCodes=u={})),function(e){e.Accept="accept",e.ContentType="content-type"}(d||(t.Headers=d={})),function(e){e.ApplicationJson="application/json"}(h||(t.MediaTypes=h={})),t.getProxyUrl=function(e){const t=l.getProxyUrl(new URL(e));return t?t.href:""};const m=[u.MovedPermanently,u.ResourceMoved,u.SeeOther,u.TemporaryRedirect,u.PermanentRedirect],g=[u.BadGateway,u.ServiceUnavailable,u.GatewayTimeout],f=["OPTIONS","GET","DELETE","HEAD"];class E extends Error{constructor(e,t){super(e),this.name="HttpClientError",this.statusCode=t,Object.setPrototypeOf(this,E.prototype)}}t.HttpClientError=E;class C{constructor(e){this.message=e}readBody(){return a(this,void 0,void 0,(function*(){return new Promise((e=>a(this,void 0,void 0,(function*(){let t=Buffer.alloc(0);this.message.on("data",(e=>{t=Buffer.concat([t,e])})),this.message.on("end",(()=>{e(t.toString())}))}))))}))}readBodyBuffer(){return a(this,void 0,void 0,(function*(){return new Promise((e=>a(this,void 0,void 0,(function*(){const t=[];this.message.on("data",(e=>{t.push(e)})),this.message.on("end",(()=>{e(Buffer.concat(t))}))}))))}))}}t.HttpClientResponse=C,t.isHttps=function(e){return"https:"===new URL(e).protocol},t.HttpClient=class{constructor(e,t,i){this._ignoreSslError=!1,this._allowRedirects=!0,this._allowRedirectDowngrade=!1,this._maxRedirects=50,this._allowRetries=!1,this._maxRetries=1,this._keepAlive=!1,this._disposed=!1,this.userAgent=e,this.handlers=t||[],this.requestOptions=i,i&&(null!=i.ignoreSslError&&(this._ignoreSslError=i.ignoreSslError),this._socketTimeout=i.socketTimeout,null!=i.allowRedirects&&(this._allowRedirects=i.allowRedirects),null!=i.allowRedirectDowngrade&&(this._allowRedirectDowngrade=i.allowRedirectDowngrade),null!=i.maxRedirects&&(this._maxRedirects=Math.max(i.maxRedirects,0)),null!=i.keepAlive&&(this._keepAlive=i.keepAlive),null!=i.allowRetries&&(this._allowRetries=i.allowRetries),null!=i.maxRetries&&(this._maxRetries=i.maxRetries))}options(e,t){return a(this,void 0,void 0,(function*(){return this.request("OPTIONS",e,null,t||{})}))}get(e,t){return a(this,void 0,void 0,(function*(){return this.request("GET",e,null,t||{})}))}del(e,t){return a(this,void 0,void 0,(function*(){return this.request("DELETE",e,null,t||{})}))}post(e,t,i){return a(this,void 0,void 0,(function*(){return this.request("POST",e,t,i||{})}))}patch(e,t,i){return a(this,void 0,void 0,(function*(){return this.request("PATCH",e,t,i||{})}))}put(e,t,i){return a(this,void 0,void 0,(function*(){return this.request("PUT",e,t,i||{})}))}head(e,t){return a(this,void 0,void 0,(function*(){return this.request("HEAD",e,null,t||{})}))}sendStream(e,t,i,s){return a(this,void 0,void 0,(function*(){return this.request(e,t,i,s)}))}getJson(e,t={}){return a(this,void 0,void 0,(function*(){t[d.Accept]=this._getExistingOrDefaultHeader(t,d.Accept,h.ApplicationJson);const i=yield this.get(e,t);return this._processResponse(i,this.requestOptions)}))}postJson(e,t,i={}){return a(this,void 0,void 0,(function*(){const s=JSON.stringify(t,null,2);i[d.Accept]=this._getExistingOrDefaultHeader(i,d.Accept,h.ApplicationJson),i[d.ContentType]=this._getExistingOrDefaultHeader(i,d.ContentType,h.ApplicationJson);const r=yield this.post(e,s,i);return this._processResponse(r,this.requestOptions)}))}putJson(e,t,i={}){return a(this,void 0,void 0,(function*(){const s=JSON.stringify(t,null,2);i[d.Accept]=this._getExistingOrDefaultHeader(i,d.Accept,h.ApplicationJson),i[d.ContentType]=this._getExistingOrDefaultHeader(i,d.ContentType,h.ApplicationJson);const r=yield this.put(e,s,i);return this._processResponse(r,this.requestOptions)}))}patchJson(e,t,i={}){return a(this,void 0,void 0,(function*(){const s=JSON.stringify(t,null,2);i[d.Accept]=this._getExistingOrDefaultHeader(i,d.Accept,h.ApplicationJson),i[d.ContentType]=this._getExistingOrDefaultHeader(i,d.ContentType,h.ApplicationJson);const r=yield this.patch(e,s,i);return this._processResponse(r,this.requestOptions)}))}request(e,t,i,s){return a(this,void 0,void 0,(function*(){if(this._disposed)throw new Error("Client has already been disposed.");const r=new URL(t);let n=this._prepareRequest(e,r,s);const a=this._allowRetries&&f.includes(e)?this._maxRetries+1:1;let o,c=0;do{if(o=yield this.requestRaw(n,i),o&&o.message&&o.message.statusCode===u.Unauthorized){let e;for(const t of this.handlers)if(t.canHandleAuthentication(o)){e=t;break}return e?e.handleAuthentication(this,n,i):o}let t=this._maxRedirects;for(;o.message.statusCode&&m.includes(o.message.statusCode)&&this._allowRedirects&&t>0;){const a=o.message.headers.location;if(!a)break;const c=new URL(a);if("https:"===r.protocol&&r.protocol!==c.protocol&&!this._allowRedirectDowngrade)throw new Error("Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.");if(yield o.readBody(),c.hostname!==r.hostname)for(const e in s)"authorization"===e.toLowerCase()&&delete s[e];n=this._prepareRequest(e,c,s),o=yield this.requestRaw(n,i),t--}if(!o.message.statusCode||!g.includes(o.message.statusCode))return o;c+=1,c{this.requestRawWithCallback(e,t,(function(e,t){e?s(e):t?i(t):s(new Error("Unknown error"))}))}))}))}requestRawWithCallback(e,t,i){"string"==typeof t&&(e.options.headers||(e.options.headers={}),e.options.headers["Content-Length"]=Buffer.byteLength(t,"utf8"));let s=!1;function r(e,t){s||(s=!0,i(e,t))}const n=e.httpModule.request(e.options,(e=>{r(void 0,new C(e))}));let a;n.on("socket",(e=>{a=e})),n.setTimeout(this._socketTimeout||18e4,(()=>{a&&a.end(),r(new Error(`Request timeout: ${e.options.path}`))})),n.on("error",(function(e){r(e)})),t&&"string"==typeof t&&n.write(t,"utf8"),t&&"string"!=typeof t?(t.on("close",(function(){n.end()})),t.pipe(n)):n.end()}getAgent(e){const t=new URL(e);return this._getAgent(t)}getAgentDispatcher(e){const t=new URL(e),i=l.getProxyUrl(t);if(i&&i.hostname)return this._getProxyAgentDispatcher(t,i)}_prepareRequest(e,t,i){const s={};s.parsedUrl=t;const r="https:"===s.parsedUrl.protocol;s.httpModule=r?c:o;const n=r?443:80;if(s.options={},s.options.host=s.parsedUrl.hostname,s.options.port=s.parsedUrl.port?parseInt(s.parsedUrl.port):n,s.options.path=(s.parsedUrl.pathname||"")+(s.parsedUrl.search||""),s.options.method=e,s.options.headers=this._mergeHeaders(i),null!=this.userAgent&&(s.options.headers["user-agent"]=this.userAgent),s.options.agent=this._getAgent(s.parsedUrl),this.handlers)for(const e of this.handlers)e.prepareRequest(s.options);return s}_mergeHeaders(e){return this.requestOptions&&this.requestOptions.headers?Object.assign({},y(this.requestOptions.headers),y(e||{})):y(e||{})}_getExistingOrDefaultHeader(e,t,i){let s;return this.requestOptions&&this.requestOptions.headers&&(s=y(this.requestOptions.headers)[t]),e[t]||s||i}_getAgent(e){let t;const i=l.getProxyUrl(e),s=i&&i.hostname;if(this._keepAlive&&s&&(t=this._proxyAgent),this._keepAlive&&!s&&(t=this._agent),t)return t;const r="https:"===e.protocol;let n=100;if(this.requestOptions&&(n=this.requestOptions.maxSockets||o.globalAgent.maxSockets),i&&i.hostname){const e={maxSockets:n,keepAlive:this._keepAlive,proxy:Object.assign(Object.assign({},(i.username||i.password)&&{proxyAuth:`${i.username}:${i.password}`}),{host:i.hostname,port:i.port})};let s;const a="https:"===i.protocol;s=r?a?p.httpsOverHttps:p.httpsOverHttp:a?p.httpOverHttps:p.httpOverHttp,t=s(e),this._proxyAgent=t}if(this._keepAlive&&!t){const e={keepAlive:this._keepAlive,maxSockets:n};t=r?new c.Agent(e):new o.Agent(e),this._agent=t}return t||(t=r?c.globalAgent:o.globalAgent),r&&this._ignoreSslError&&(t.options=Object.assign(t.options||{},{rejectUnauthorized:!1})),t}_getProxyAgentDispatcher(e,t){let i;if(this._keepAlive&&(i=this._proxyAgentDispatcher),i)return i;const s="https:"===e.protocol;return i=new A.ProxyAgent(Object.assign({uri:t.href,pipelining:this._keepAlive?1:0},(t.username||t.password)&&{token:`${t.username}:${t.password}`})),this._proxyAgentDispatcher=i,s&&this._ignoreSslError&&(i.options=Object.assign(i.options.requestTls||{},{rejectUnauthorized:!1})),i}_performExponentialBackoff(e){return a(this,void 0,void 0,(function*(){e=Math.min(10,e);const t=5*Math.pow(2,e);return new Promise((e=>setTimeout((()=>e()),t)))}))}_processResponse(e,t){return a(this,void 0,void 0,(function*(){return new Promise(((i,s)=>a(this,void 0,void 0,(function*(){const r=e.message.statusCode||0,n={statusCode:r,result:null,headers:{}};let a,o;r===u.NotFound&&i(n);try{o=yield e.readBody(),o&&o.length>0&&(a=t&&t.deserializeDates?JSON.parse(o,(function(e,t){if("string"==typeof t){const e=new Date(t);if(!isNaN(e.valueOf()))return e}return t})):JSON.parse(o),n.result=a),n.headers=e.message.headers}catch(e){}if(r>299){let e;e=a&&a.message?a.message:o&&o.length>0?o:`Failed request: (${r})`;const t=new E(e,r);t.result=n.result,s(t)}else i(n)}))))}))}};const y=e=>Object.keys(e).reduce(((t,i)=>(t[i.toLowerCase()]=e[i],t)),{})},20359:(e,t)=>{"use strict";function i(e){if(!e.hostname)return!1;if(function(e){const t=e.toLowerCase();return"localhost"===t||t.startsWith("127.")||t.startsWith("[::1]")||t.startsWith("[0:0:0:0:0:0:0:1]")}(e.hostname))return!0;const t=process.env.no_proxy||process.env.NO_PROXY||"";if(!t)return!1;let i;e.port?i=Number(e.port):"http:"===e.protocol?i=80:"https:"===e.protocol&&(i=443);const s=[e.hostname.toUpperCase()];"number"==typeof i&&s.push(`${s[0]}:${i}`);for(const e of t.split(",").map((e=>e.trim().toUpperCase())).filter((e=>e)))if("*"===e||s.some((t=>t===e||t.endsWith(`.${e}`)||e.startsWith(".")&&t.endsWith(`${e}`))))return!0;return!1}Object.defineProperty(t,"__esModule",{value:!0}),t.checkBypass=t.getProxyUrl=void 0,t.getProxyUrl=function(e){const t="https:"===e.protocol;if(i(e))return;const s=t?process.env.https_proxy||process.env.HTTPS_PROXY:process.env.http_proxy||process.env.HTTP_PROXY;if(s)try{return new URL(s)}catch(e){if(!s.startsWith("http://")&&!s.startsWith("https://"))return new URL(`http://${s}`)}},t.checkBypass=i},55763:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){for(var i in e)t.hasOwnProperty(i)||(t[i]=e[i])}(i(20536))},20536:function(e,t,i){"use strict";var s=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const r=i(57147),n=s(i(8856)).default("@kwsites/file-exists");t.exists=function(e,i=t.READABLE){return function(e,t,i){n("checking %s",e);try{const s=r.statSync(e);return s.isFile()&&t?(n("[OK] path represents a file"),!0):s.isDirectory()&&i?(n("[OK] path represents a directory"),!0):(n("[FAIL] path represents something other than a file or directory"),!1)}catch(e){if("ENOENT"===e.code)return n("[FAIL] path is not accessible: %o",e),!1;throw n("[FATAL] %o",e),e}}(e,(i&t.FILE)>0,(i&t.FOLDER)>0)},t.FILE=1,t.FOLDER=2,t.READABLE=t.FILE+t.FOLDER},39487:(e,t)=>{"use strict";function i(){let e,t,i="pending";return{promise:new Promise(((i,s)=>{e=i,t=s})),done(t){"pending"===i&&(i="resolved",e(t))},fail(e){"pending"===i&&(i="rejected",t(e))},get fulfilled(){return"pending"!==i},get status(){return i}}}Object.defineProperty(t,"__esModule",{value:!0}),t.createDeferred=t.deferred=void 0,t.deferred=i,t.createDeferred=i,t.default=i},8518:(e,t,i)=>{"use strict";i.r(t),i.d(t,{createOAuthAppAuth:()=>u,createOAuthUserAuth:()=>o.createOAuthUserAuth});var s=i(21375),r=i(85111),n=i(96756),a=i.n(n),o=i(40642);async function c(e,t){if("oauth-app"===t.type)return{type:"oauth-app",clientId:e.clientId,clientSecret:e.clientSecret,clientType:e.clientType,headers:{authorization:`basic ${a()(`${e.clientId}:${e.clientSecret}`)}`}};if("factory"in t){const{type:i,...s}={...t,...e};return t.factory(s)}const i={clientId:e.clientId,clientSecret:e.clientSecret,request:e.request,...t};return(e.clientType,await(0,o.createOAuthUserAuth)({...i,clientType:e.clientType}))()}var l=i(96882);async function p(e,t,i,s){let r=t.endpoint.merge(i,s);if(/\/login\/(oauth\/access_token|device\/code)$/.test(r.url))return t(r);if("github-app"===e.clientType&&!(0,l.X)(r.url))throw new Error(`[@octokit/auth-oauth-app] GitHub Apps cannot use their client ID/secret for basic authentication for endpoints other than "/applications/{client_id}/**". "${r.method} ${r.url}" is not supported.`);const n=a()(`${e.clientId}:${e.clientSecret}`);r.headers.authorization=`basic ${n}`;try{return await t(r)}catch(e){if(401!==e.status)throw e;throw e.message=`[@octokit/auth-oauth-app] "${r.method} ${r.url}" does not support clientId/clientSecret basic authentication.`,e}}const A="5.0.6";function u(e){const t=Object.assign({request:r.W.defaults({headers:{"user-agent":`octokit-auth-oauth-app.js/${A} ${(0,s.getUserAgent)()}`}}),clientType:"oauth-app"},e);return Object.assign(c.bind(null,t),{hook:p.bind(null,t)})}},40642:(e,t,i)=>{"use strict";i.r(t),i.d(t,{createOAuthUserAuth:()=>Q,requiresBasicAuth:()=>B.X});var s=i(21375),r=i(85111);const n="2.1.2";var a=i(67445),o=i(16930);async function c(e,t){const i=function(e,t){if(!0===t.refresh)return!1;if(!e.authentication)return!1;if("github-app"===e.clientType)return e.authentication;const i=e.authentication;return("scopes"in t&&t.scopes||e.scopes).join(" ")===i.scopes.join(" ")&&i}(e,t.auth);if(i)return i;const{data:s}=await(0,a.T)({clientType:e.clientType,clientId:e.clientId,request:t.request||e.request,scopes:t.auth.scopes||e.scopes});await e.onVerification(s);const r=await p(t.request||e.request,e.clientId,e.clientType,s);return e.authentication=r,r}async function l(e){await new Promise((t=>setTimeout(t,1e3*e)))}async function p(e,t,i,s){try{const r={clientId:t,request:e,code:s.device_code},{authentication:n}="oauth-app"===i?await(0,o.i)({...r,clientType:"oauth-app"}):await(0,o.i)({...r,clientType:"github-app"});return{type:"token",tokenType:"oauth",...n}}catch(r){if(!r.response)throw r;const n=r.response.data.error;if("authorization_pending"===n)return await l(s.interval),p(e,t,i,s);if("slow_down"===n)return await l(s.interval+5),p(e,t,i,s);throw r}}async function A(e,t){return c(e,{auth:t})}async function u(e,t,i,s){let r=t.endpoint.merge(i,s);if(/\/login\/(oauth\/access_token|device\/code)$/.test(r.url))return t(r);const{token:n}=await c(e,{request:t,auth:{type:"oauth"}});return r.headers.authorization=`token ${n}`,t(r)}const d="4.0.5";function h(e){const t=e.request||r.W.defaults({headers:{"user-agent":`octokit-auth-oauth-device.js/${d} ${(0,s.getUserAgent)()}`}}),{request:i=t,...n}=e,a="github-app"===e.clientType?{...n,clientType:"github-app",request:i}:{...n,clientType:"oauth-app",request:i,scopes:e.scopes||[]};if(!e.clientId)throw new Error('[@octokit/auth-oauth-device] "clientId" option must be set (https://github.com/octokit/auth-oauth-device.js#usage)');if(!e.onVerification)throw new Error('[@octokit/auth-oauth-device] "onVerification" option must be a function (https://github.com/octokit/auth-oauth-device.js#usage)');return Object.assign(A.bind(null,a),{hook:u.bind(null,a)})}var m=i(90347);var g=i(88655),f=i(57633),E=i(44373),C=i(83818),y=i(20217);async function v(e,t={}){if(e.authentication||(e.authentication=(e.clientType,await async function(e){if("code"in e.strategyOptions){const{authentication:t}=await(0,m.y)({clientId:e.clientId,clientSecret:e.clientSecret,clientType:e.clientType,onTokenCreated:e.onTokenCreated,...e.strategyOptions,request:e.request});return{type:"token",tokenType:"oauth",...t}}if("onVerification"in e.strategyOptions){const t=h({clientType:e.clientType,clientId:e.clientId,onTokenCreated:e.onTokenCreated,...e.strategyOptions,request:e.request}),i=await t({type:"oauth"});return{clientSecret:e.clientSecret,...i}}if("token"in e.strategyOptions)return{type:"token",tokenType:"oauth",clientId:e.clientId,clientSecret:e.clientSecret,clientType:e.clientType,onTokenCreated:e.onTokenCreated,...e.strategyOptions};throw new Error("[@octokit/auth-oauth-user] Invalid strategy options")}(e))),e.authentication.invalid)throw new Error("[@octokit/auth-oauth-user] Token is invalid");const i=e.authentication;if("expiresAt"in i&&("refresh"===t.type||new Date(i.expiresAt){"use strict";i.d(t,{X:()=>r});const s=/\/applications\/[^/]+\/(token|grant)s?/;function r(e){return e&&s.test(e)}},22282:(e,t,i)=>{"use strict";async function s(e){return{type:"unauthenticated",reason:e}}i.r(t),i.d(t,{createUnauthenticatedAuth:()=>a});var r=/\babuse\b/i;async function n(e,t,i,s){const n=t.endpoint.merge(i,s);return t(n).catch((t=>{if(404===t.status)throw t.message=`Not found. May be due to lack of authentication. Reason: ${e}`,t;if(function(e){return 403===e.status&&!!e.response&&"0"===e.response.headers["x-ratelimit-remaining"]}(t))throw t.message=`API rate limit exceeded. This maybe caused by the lack of authentication. Reason: ${e}`,t;if(function(e){return 403===e.status&&r.test(e.message)}(t))throw t.message=`You have triggered an abuse detection mechanism. This maybe caused by the lack of authentication. Reason: ${e}`,t;if(401===t.status)throw t.message=`Unauthorized. "${n.method} ${n.url}" failed most likely due to lack of authentication. Reason: ${e}`,t;throw t.status>=400&&t.status<500&&(t.message=t.message.replace(/\.?$/,`. May be caused by lack of authentication (${e}).`)),t}))}var a=function(e){if(!e||!e.reason)throw new Error("[@octokit/auth-unauthenticated] No reason passed to createUnauthenticatedAuth");return Object.assign(s.bind(null,e.reason),{hook:n.bind(null,e.reason)})}},77960:(e,t,i)=>{"use strict";i.r(t),i.d(t,{Octokit:()=>E});var s=i(21375),r=i(8903),n=i(85111);class a extends Error{constructor(e,t,i){super("Request failed due to following response errors:\n"+i.errors.map((e=>` - ${e.message}`)).join("\n")),this.request=e,this.headers=t,this.response=i,this.name="GraphqlResponseError",this.errors=i.errors,this.data=i.data,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}}const o=["method","baseUrl","url","headers","request","query","mediaType"],c=["query","method","url"],l=/\/api\/v3\/?$/;function p(e,t){const i=e.defaults(t);return Object.assign(((e,t)=>function(e,t,i){if(i){if("string"==typeof t&&"query"in i)return Promise.reject(new Error('[@octokit/graphql] "query" cannot be used as variable name'));for(const e in i)if(c.includes(e))return Promise.reject(new Error(`[@octokit/graphql] "${e}" cannot be used as variable name`))}const s="string"==typeof t?Object.assign({query:t},i):t,r=Object.keys(s).reduce(((e,t)=>o.includes(t)?(e[t]=s[t],e):(e.variables||(e.variables={}),e.variables[t]=s[t],e)),{}),n=s.baseUrl||e.endpoint.DEFAULTS.baseUrl;return l.test(n)&&(r.url=n.replace(l,"/api/graphql")),e(r).then((e=>{if(e.data.errors){const t={};for(const i of Object.keys(e.headers))t[i]=e.headers[i];throw new a(r,t,e.data)}return e.data.data}))}(i,e,t)),{defaults:p.bind(null,i),endpoint:i.endpoint})}p(n.W,{headers:{"user-agent":`octokit-graphql.js/5.0.5 ${(0,s.getUserAgent)()}`},method:"POST",url:"/graphql"});const A=/^v1\./,u=/^ghs_/,d=/^ghu_/;async function h(e){const t=3===e.split(/\./).length,i=A.test(e)||u.test(e),s=d.test(e);return{type:"token",token:e,tokenType:t?"app":i?"installation":s?"user-to-server":"oauth"}}async function m(e,t,i,s){const r=t.endpoint.merge(i,s);return r.headers.authorization=function(e){return 3===e.split(/\./).length?`bearer ${e}`:`token ${e}`}(e),t(r)}const g=function(e){if(!e)throw new Error("[@octokit/auth-token] No token passed to createTokenAuth");if("string"!=typeof e)throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string");return e=e.replace(/^(token|bearer) +/i,""),Object.assign(h.bind(null,e),{hook:m.bind(null,e)})};var f="4.2.4",E=class{static defaults(e){return class extends(this){constructor(...t){const i=t[0]||{};super("function"!=typeof e?Object.assign({},e,i,i.userAgent&&e.userAgent?{userAgent:`${i.userAgent} ${e.userAgent}`}:null):e(i))}}}static plugin(...e){var t;const i=this.plugins;return(t=class extends(this){}).plugins=i.concat(e.filter((e=>!i.includes(e)))),t}constructor(e={}){const t=new r.Collection,i={baseUrl:n.W.endpoint.DEFAULTS.baseUrl,headers:{},request:Object.assign({},e.request,{hook:t.bind(null,"request")}),mediaType:{previews:[],format:""}};var a;if(i.headers["user-agent"]=[e.userAgent,`octokit-core.js/${f} ${(0,s.getUserAgent)()}`].filter(Boolean).join(" "),e.baseUrl&&(i.baseUrl=e.baseUrl),e.previews&&(i.mediaType.previews=e.previews),e.timeZone&&(i.headers["time-zone"]=e.timeZone),this.request=n.W.defaults(i),this.graphql=(a=this.request,p(a,{method:"POST",url:"/graphql"})).defaults(i),this.log=Object.assign({debug:()=>{},info:()=>{},warn:console.warn.bind(console),error:console.error.bind(console)},e.log),this.hook=t,e.authStrategy){const{authStrategy:i,...s}=e,r=i(Object.assign({request:this.request,log:this.log,octokit:this,octokitOptions:s},e.auth));t.wrap("request",r.hook),this.auth=r}else if(e.auth){const i=g(e.auth);t.wrap("request",i.hook),this.auth=i}else this.auth=async()=>({type:"unauthenticated"});this.constructor.plugins.forEach((t=>{Object.assign(this,t(this,e))}))}};E.VERSION=f,E.plugins=[]},60634:(e,t,i)=>{"use strict";i.r(t),i.d(t,{GraphqlResponseError:()=>S,graphql:()=>N,withCustomRequest:()=>L});var s=i(3196),r=i(21375);function n(e,t){const i=Object.assign({},e);return Object.keys(t).forEach((r=>{(0,s.P)(t[r])?r in e?i[r]=n(e[r],t[r]):Object.assign(i,{[r]:t[r]}):Object.assign(i,{[r]:t[r]})})),i}function a(e){for(const t in e)void 0===e[t]&&delete e[t];return e}function o(e,t,i){if("string"==typeof t){let[e,s]=t.split(" ");i=Object.assign(s?{method:e,url:s}:{url:e},i)}else i=Object.assign({},t);var s;i.headers=(s=i.headers)?Object.keys(s).reduce(((e,t)=>(e[t.toLowerCase()]=s[t],e)),{}):{},a(i),a(i.headers);const r=n(e||{},i);return e&&e.mediaType.previews.length&&(r.mediaType.previews=e.mediaType.previews.filter((e=>!r.mediaType.previews.includes(e))).concat(r.mediaType.previews)),r.mediaType.previews=r.mediaType.previews.map((e=>e.replace(/-preview/,""))),r}const c=/\{[^}]+\}/g;function l(e){return e.replace(/^\W+|\W+$/g,"").split(/,/)}function p(e,t){return Object.keys(e).filter((e=>!t.includes(e))).reduce(((t,i)=>(t[i]=e[i],t)),{})}function A(e){return e.split(/(%[0-9A-Fa-f]{2})/g).map((function(e){return/%[0-9A-Fa-f]/.test(e)||(e=encodeURI(e).replace(/%5B/g,"[").replace(/%5D/g,"]")),e})).join("")}function u(e){return encodeURIComponent(e).replace(/[!'()*]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}))}function d(e,t,i){return t="+"===e||"#"===e?A(t):u(t),i?u(i)+"="+t:t}function h(e){return null!=e}function m(e){return";"===e||"&"===e||"?"===e}function g(e,t){var i=["+","#",".","/",";","?","&"];return e.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g,(function(e,s,r){if(s){let e="";const r=[];if(-1!==i.indexOf(s.charAt(0))&&(e=s.charAt(0),s=s.substr(1)),s.split(/,/g).forEach((function(i){var s=/([^:\*]*)(?::(\d+)|(\*))?/.exec(i);r.push(function(e,t,i,s){var r=e[i],n=[];if(h(r)&&""!==r)if("string"==typeof r||"number"==typeof r||"boolean"==typeof r)r=r.toString(),s&&"*"!==s&&(r=r.substring(0,parseInt(s,10))),n.push(d(t,r,m(t)?i:""));else if("*"===s)Array.isArray(r)?r.filter(h).forEach((function(e){n.push(d(t,e,m(t)?i:""))})):Object.keys(r).forEach((function(e){h(r[e])&&n.push(d(t,r[e],e))}));else{const e=[];Array.isArray(r)?r.filter(h).forEach((function(i){e.push(d(t,i))})):Object.keys(r).forEach((function(i){h(r[i])&&(e.push(u(i)),e.push(d(t,r[i].toString())))})),m(t)?n.push(u(i)+"="+e.join(",")):0!==e.length&&n.push(e.join(","))}else";"===t?h(r)&&n.push(u(i)):""!==r||"&"!==t&&"?"!==t?""===r&&n.push(""):n.push(u(i)+"=");return n}(t,e,s[1],s[2]||s[3]))})),e&&"+"!==e){var n=",";return"?"===e?n="&":"#"!==e&&(n=e),(0!==r.length?e:"")+r.join(n)}return r.join(",")}return A(r)}))}function f(e){let t,i=e.method.toUpperCase(),s=(e.url||"/").replace(/:([a-z]\w+)/g,"{$1}"),r=Object.assign({},e.headers),n=p(e,["method","baseUrl","url","headers","request","mediaType"]);const a=function(e){const t=e.match(c);return t?t.map(l).reduce(((e,t)=>e.concat(t)),[]):[]}(s);var o;s=(o=s,{expand:g.bind(null,o)}).expand(n),/^http/.test(s)||(s=e.baseUrl+s);const A=p(n,Object.keys(e).filter((e=>a.includes(e))).concat("baseUrl"));if(!/application\/octet-stream/i.test(r.accept)&&(e.mediaType.format&&(r.accept=r.accept.split(/,/).map((t=>t.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/,`application/vnd$1$2.${e.mediaType.format}`))).join(",")),e.mediaType.previews.length)){const t=r.accept.match(/[\w-]+(?=-preview)/g)||[];r.accept=t.concat(e.mediaType.previews).map((t=>`application/vnd.github.${t}-preview${e.mediaType.format?`.${e.mediaType.format}`:"+json"}`)).join(",")}return["GET","HEAD"].includes(i)?s=function(e,t){const i=/\?/.test(e)?"&":"?",s=Object.keys(t);return 0===s.length?e:e+i+s.map((e=>"q"===e?"q="+t.q.split("+").map(encodeURIComponent).join("+"):`${e}=${encodeURIComponent(t[e])}`)).join("&")}(s,A):"data"in A?t=A.data:Object.keys(A).length?t=A:r["content-length"]=0,r["content-type"]||void 0===t||(r["content-type"]="application/json; charset=utf-8"),["PATCH","PUT"].includes(i)&&void 0===t&&(t=""),Object.assign({method:i,url:s,headers:r},void 0!==t?{body:t}:null,e.request?{request:e.request}:null)}function E(e,t,i){return f(o(e,t,i))}const C=function e(t,i){const s=o(t,i),r=E.bind(null,s);return Object.assign(r,{DEFAULTS:s,defaults:e.bind(null,s),merge:o.bind(null,s),parse:f})}(null,{method:"GET",baseUrl:"https://api.github.com",headers:{accept:"application/vnd.github.v3+json","user-agent":`octokit-endpoint.js/6.0.12 ${(0,r.getUserAgent)()}`},mediaType:{format:"",previews:[]}});var y=i(51143),v=i(57751),w=i(36219),I=i.n(w);const B=I()((e=>console.warn(e))),b=I()((e=>console.warn(e)));class Q extends Error{constructor(e,t,i){let s;super(e),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name="HttpError",this.status=t,"headers"in i&&void 0!==i.headers&&(s=i.headers),"response"in i&&(this.response=i.response,s=i.response.headers);const r=Object.assign({},i.request);i.request.headers.authorization&&(r.headers=Object.assign({},i.request.headers,{authorization:i.request.headers.authorization.replace(/ .*$/," [REDACTED]")})),r.url=r.url.replace(/\bclient_secret=\w+/g,"client_secret=[REDACTED]").replace(/\baccess_token=\w+/g,"access_token=[REDACTED]"),this.request=r,Object.defineProperty(this,"code",{get:()=>(B(new v.$("[@octokit/request-error] `error.code` is deprecated, use `error.status`.")),t)}),Object.defineProperty(this,"headers",{get:()=>(b(new v.$("[@octokit/request-error] `error.headers` is deprecated, use `error.response.headers`.")),s||{})})}}function x(e){const t=e.request&&e.request.log?e.request.log:console;((0,s.P)(e.body)||Array.isArray(e.body))&&(e.body=JSON.stringify(e.body));let i,r,n={};return(e.request&&e.request.fetch||y.ZP)(e.url,Object.assign({method:e.method,body:e.body,headers:e.headers,redirect:e.redirect},e.request)).then((async s=>{r=s.url,i=s.status;for(const e of s.headers)n[e[0]]=e[1];if("deprecation"in n){const i=n.link&&n.link.match(/<([^>]+)>; rel="deprecation"/),s=i&&i.pop();t.warn(`[@octokit/request] "${e.method} ${e.url}" is deprecated. It is scheduled to be removed on ${n.sunset}${s?`. See ${s}`:""}`)}if(204!==i&&205!==i){if("HEAD"===e.method){if(i<400)return;throw new Q(s.statusText,i,{response:{url:r,status:i,headers:n,data:void 0},request:e})}if(304===i)throw new Q("Not modified",i,{response:{url:r,status:i,headers:n,data:await k(s)},request:e});if(i>=400){const t=await k(s),a=new Q(function(e){return"string"==typeof e?e:"message"in e?Array.isArray(e.errors)?`${e.message}: ${e.errors.map(JSON.stringify).join(", ")}`:e.message:`Unknown error: ${JSON.stringify(e)}`}(t),i,{response:{url:r,status:i,headers:n,data:t},request:e});throw a}return k(s)}})).then((e=>({status:i,url:r,headers:n,data:e}))).catch((t=>{if(t instanceof Q)throw t;throw new Q(t.message,500,{request:e})}))}async function k(e){const t=e.headers.get("content-type");return/application\/json/.test(t)?e.json():!t||/^text\/|charset=utf-8$/.test(t)?e.text():function(e){return e.arrayBuffer()}(e)}const D=function e(t,i){const s=t.defaults(i);return Object.assign((function(t,i){const r=s.merge(t,i);if(!r.request||!r.request.hook)return x(s.parse(r));const n=(e,t)=>x(s.parse(s.merge(e,t)));return Object.assign(n,{endpoint:s,defaults:e.bind(null,s)}),r.request.hook(n,r)}),{endpoint:s,defaults:e.bind(null,s)})}(C,{headers:{"user-agent":`octokit-request.js/5.6.3 ${(0,r.getUserAgent)()}`}});class S extends Error{constructor(e,t,i){super("Request failed due to following response errors:\n"+i.errors.map((e=>` - ${e.message}`)).join("\n")),this.request=e,this.headers=t,this.response=i,this.name="GraphqlResponseError",this.errors=i.errors,this.data=i.data,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}}const _=["method","baseUrl","url","headers","request","query","mediaType"],R=["query","method","url"],T=/\/api\/v3\/?$/;function F(e,t){const i=e.defaults(t);return Object.assign(((e,t)=>function(e,t,i){if(i){if("string"==typeof t&&"query"in i)return Promise.reject(new Error('[@octokit/graphql] "query" cannot be used as variable name'));for(const e in i)if(R.includes(e))return Promise.reject(new Error(`[@octokit/graphql] "${e}" cannot be used as variable name`))}const s="string"==typeof t?Object.assign({query:t},i):t,r=Object.keys(s).reduce(((e,t)=>_.includes(t)?(e[t]=s[t],e):(e.variables||(e.variables={}),e.variables[t]=s[t],e)),{}),n=s.baseUrl||e.endpoint.DEFAULTS.baseUrl;return T.test(n)&&(r.url=n.replace(T,"/api/graphql")),e(r).then((e=>{if(e.data.errors){const t={};for(const i of Object.keys(e.headers))t[i]=e.headers[i];throw new S(r,t,e.data)}return e.data.data}))}(i,e,t)),{defaults:F.bind(null,i),endpoint:D.endpoint})}const N=F(D,{headers:{"user-agent":`octokit-graphql.js/4.8.0 ${(0,r.getUserAgent)()}`},method:"POST",url:"/graphql"});function L(e){return F(e,{method:"POST",url:"/graphql"})}},44929:(e,t,i)=>{"use strict";var s,r=Object.create,n=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,c=Object.getPrototypeOf,l=Object.prototype.hasOwnProperty,p=(e,t,i,s)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let r of o(t))l.call(e,r)||r===i||n(e,r,{get:()=>t[r],enumerable:!(s=a(t,r))||s.enumerable});return e},A=(e,t,i)=>(i=null!=e?r(c(e)):{},p(!t&&e&&e.__esModule?i:n(i,"default",{value:e,enumerable:!0}),e)),u={};((e,t)=>{for(var i in t)n(e,i,{get:t[i],enumerable:!0})})(u,{OAuthApp:()=>ne,createAWSLambdaAPIGatewayV2Handler:()=>re,createCloudflareHandler:()=>ee,createNodeMiddleware:()=>z,createWebWorkerHandler:()=>Z,handleRequest:()=>J}),e.exports=(s=u,p(n({},"__esModule",{value:!0}),s));var d=i(8518),h="4.2.4";function m(e,t,i){if(Array.isArray(t))for(const s of t)m(e,s,i);else e.eventHandlers[t]||(e.eventHandlers[t]=[]),e.eventHandlers[t].push(i)}var g=i(77960),f=i(21375),E=g.Octokit.defaults({userAgent:`octokit-oauth-app.js/${h} ${(0,f.getUserAgent)()}`}),C=i(40642);async function y(e,t){const{name:i,action:s}=t;if(e.eventHandlers[`${i}.${s}`])for(const r of e.eventHandlers[`${i}.${s}`])await r(t);if(e.eventHandlers[i])for(const s of e.eventHandlers[i])await s(t)}async function v(e,t){return e.octokit.auth({type:"oauth-user",...t,async factory(t){const i=new e.Octokit({authStrategy:C.createOAuthUserAuth,auth:t}),s=await i.auth({type:"get"});return await y(e,{name:"token",action:"created",token:s.token,scopes:s.scopes,authentication:s,octokit:i}),i}})}var w=A(i(26925));function I(e,t){const i={clientId:e.clientId,request:e.octokit.request,...t,allowSignup:e.allowSignup??t.allowSignup,redirectUrl:t.redirectUrl??e.redirectUrl,scopes:t.scopes??e.defaultScopes};return w.getWebFlowAuthorizationUrl({clientType:e.clientType,...i})}var B=A(i(8518));async function b(e,t){const i=await e.octokit.auth({type:"oauth-user",...t});return await y(e,{name:"token",action:"created",token:i.token,scopes:i.scopes,authentication:i,octokit:new e.Octokit({authStrategy:B.createOAuthUserAuth,auth:{clientType:e.clientType,clientId:e.clientId,clientSecret:e.clientSecret,token:i.token,scopes:i.scopes,refreshToken:i.refreshToken,expiresAt:i.expiresAt,refreshTokenExpiresAt:i.refreshTokenExpiresAt}})}),{authentication:i}}var Q=A(i(26925));async function x(e,t){const i=await Q.checkToken({clientType:e.clientType,clientId:e.clientId,clientSecret:e.clientSecret,request:e.octokit.request,...t});return Object.assign(i.authentication,{type:"token",tokenType:"oauth"}),i}var k=A(i(26925)),D=i(40642);async function S(e,t){const i={clientId:e.clientId,clientSecret:e.clientSecret,request:e.octokit.request,...t};if("oauth-app"===e.clientType){const t=await k.resetToken({clientType:"oauth-app",...i}),s=Object.assign(t.authentication,{type:"token",tokenType:"oauth"});return await y(e,{name:"token",action:"reset",token:t.authentication.token,scopes:t.authentication.scopes||void 0,authentication:s,octokit:new e.Octokit({authStrategy:D.createOAuthUserAuth,auth:{clientType:e.clientType,clientId:e.clientId,clientSecret:e.clientSecret,token:t.authentication.token,scopes:t.authentication.scopes}})}),{...t,authentication:s}}const s=await k.resetToken({clientType:"github-app",...i}),r=Object.assign(s.authentication,{type:"token",tokenType:"oauth"});return await y(e,{name:"token",action:"reset",token:s.authentication.token,authentication:r,octokit:new e.Octokit({authStrategy:D.createOAuthUserAuth,auth:{clientType:e.clientType,clientId:e.clientId,clientSecret:e.clientSecret,token:s.authentication.token}})}),{...s,authentication:r}}var _=A(i(26925)),R=i(40642);async function T(e,t){if("oauth-app"===e.clientType)throw new Error("[@octokit/oauth-app] app.refreshToken() is not supported for OAuth Apps");const i=await _.refreshToken({clientType:"github-app",clientId:e.clientId,clientSecret:e.clientSecret,request:e.octokit.request,refreshToken:t.refreshToken}),s=Object.assign(i.authentication,{type:"token",tokenType:"oauth"});return await y(e,{name:"token",action:"refreshed",token:i.authentication.token,authentication:s,octokit:new e.Octokit({authStrategy:R.createOAuthUserAuth,auth:{clientType:e.clientType,clientId:e.clientId,clientSecret:e.clientSecret,token:i.authentication.token}})}),{...i,authentication:s}}var F=A(i(26925)),N=i(40642);async function L(e,t){if("oauth-app"===e.clientType)throw new Error("[@octokit/oauth-app] app.scopeToken() is not supported for OAuth Apps");const i=await F.scopeToken({clientType:"github-app",clientId:e.clientId,clientSecret:e.clientSecret,request:e.octokit.request,...t}),s=Object.assign(i.authentication,{type:"token",tokenType:"oauth"});return await y(e,{name:"token",action:"scoped",token:i.authentication.token,authentication:s,octokit:new e.Octokit({authStrategy:N.createOAuthUserAuth,auth:{clientType:e.clientType,clientId:e.clientId,clientSecret:e.clientSecret,token:i.authentication.token}})}),{...i,authentication:s}}var O=A(i(26925)),M=i(22282);async function U(e,t){const i={clientId:e.clientId,clientSecret:e.clientSecret,request:e.octokit.request,...t},s="oauth-app"===e.clientType?await O.deleteToken({clientType:"oauth-app",...i}):await O.deleteToken({clientType:"github-app",...i});return await y(e,{name:"token",action:"deleted",token:t.token,octokit:new e.Octokit({authStrategy:M.createUnauthenticatedAuth,auth:{reason:'Handling "token.deleted" event. The access for the token has been revoked.'}})}),s}var P=A(i(26925)),G=i(22282);async function j(e,t){const i={clientId:e.clientId,clientSecret:e.clientSecret,request:e.octokit.request,...t},s="oauth-app"===e.clientType?await P.deleteAuthorization({clientType:"oauth-app",...i}):await P.deleteAuthorization({clientType:"github-app",...i});return await y(e,{name:"token",action:"deleted",token:t.token,octokit:new e.Octokit({authStrategy:G.createUnauthenticatedAuth,auth:{reason:'Handling "token.deleted" event. The access for the token has been revoked.'}})}),await y(e,{name:"authorization",action:"deleted",token:t.token,octokit:new e.Octokit({authStrategy:G.createUnauthenticatedAuth,auth:{reason:'Handling "authorization.deleted" event. The access for the app has been revoked.'}})}),s}var V=A(i(94062));async function J(e,{pathPrefix:t="/api/github/oauth"},i){var s,r,n,a,o,c;if("OPTIONS"===i.method)return{status:200,headers:{"access-control-allow-origin":"*","access-control-allow-methods":"*","access-control-allow-headers":"Content-Type, User-Agent, Authorization"}};const{pathname:l}=new URL(i.url,"http://localhost"),p=[i.method,l].join(" "),A={getLogin:`GET ${t}/login`,getCallback:`GET ${t}/callback`,createToken:`POST ${t}/token`,getToken:`GET ${t}/token`,patchToken:`PATCH ${t}/token`,patchRefreshToken:`PATCH ${t}/refresh-token`,scopeToken:`POST ${t}/token/scoped`,deleteToken:`DELETE ${t}/token`,deleteGrant:`DELETE ${t}/grant`};if(!Object.values(A).includes(p))return null;let u;try{const e=await i.text();u=e?JSON.parse(e):{}}catch(e){return{status:400,headers:{"content-type":"application/json","access-control-allow-origin":"*"},text:JSON.stringify({error:"[@octokit/oauth-app] request error"})}}const{searchParams:d}=new URL(i.url,"http://localhost"),h=(0,V.default)(d),m=i.headers;try{if(p===A.getLogin){const{url:t}=e.getWebFlowAuthorizationUrl({state:h.state,scopes:h.scopes?h.scopes.split(","):void 0,allowSignup:h.allowSignup?"true"===h.allowSignup:void 0,redirectUrl:h.redirectUrl});return{status:302,headers:{location:t}}}if(p===A.getCallback){if(h.error)throw new Error(`[@octokit/oauth-app] ${h.error} ${h.error_description}`);if(!h.code)throw new Error('[@octokit/oauth-app] "code" parameter is required');const{authentication:{token:t}}=await e.createToken({code:h.code});return{status:200,headers:{"content-type":"text/html"},text:`

Token created successfully

\n\n

Your token is: ${t}. Copy it now as it cannot be shown again.

`}}if(p===A.createToken){const{code:t,redirectUrl:i}=u;if(!t)throw new Error('[@octokit/oauth-app] "code" parameter is required');const s=await e.createToken({code:t,redirectUrl:i});return delete s.authentication.clientSecret,{status:201,headers:{"content-type":"application/json","access-control-allow-origin":"*"},text:JSON.stringify(s)}}if(p===A.getToken){const t=null==(s=m.authorization)?void 0:s.substr(6);if(!t)throw new Error('[@octokit/oauth-app] "Authorization" header is required');const i=await e.checkToken({token:t});return delete i.authentication.clientSecret,{status:200,headers:{"content-type":"application/json","access-control-allow-origin":"*"},text:JSON.stringify(i)}}if(p===A.patchToken){const t=null==(r=m.authorization)?void 0:r.substr(6);if(!t)throw new Error('[@octokit/oauth-app] "Authorization" header is required');const i=await e.resetToken({token:t});return delete i.authentication.clientSecret,{status:200,headers:{"content-type":"application/json","access-control-allow-origin":"*"},text:JSON.stringify(i)}}if(p===A.patchRefreshToken){if(!(null==(n=m.authorization)?void 0:n.substr(6)))throw new Error('[@octokit/oauth-app] "Authorization" header is required');const{refreshToken:t}=u;if(!t)throw new Error("[@octokit/oauth-app] refreshToken must be sent in request body");const i=await e.refreshToken({refreshToken:t});return delete i.authentication.clientSecret,{status:200,headers:{"content-type":"application/json","access-control-allow-origin":"*"},text:JSON.stringify(i)}}if(p===A.scopeToken){const t=null==(a=m.authorization)?void 0:a.substr(6);if(!t)throw new Error('[@octokit/oauth-app] "Authorization" header is required');const i=await e.scopeToken({token:t,...u});return delete i.authentication.clientSecret,{status:200,headers:{"content-type":"application/json","access-control-allow-origin":"*"},text:JSON.stringify(i)}}if(p===A.deleteToken){const t=null==(o=m.authorization)?void 0:o.substr(6);if(!t)throw new Error('[@octokit/oauth-app] "Authorization" header is required');return await e.deleteToken({token:t}),{status:204,headers:{"access-control-allow-origin":"*"}}}const t=null==(c=m.authorization)?void 0:c.substr(6);if(!t)throw new Error('[@octokit/oauth-app] "Authorization" header is required');return await e.deleteAuthorization({token:t}),{status:204,headers:{"access-control-allow-origin":"*"}}}catch(e){return{status:400,headers:{"content-type":"application/json","access-control-allow-origin":"*"},text:JSON.stringify({error:e.message})}}}function H(e){const{method:t,url:i,headers:s}=e;return{method:t,url:i,headers:s,text:async function(){return await new Promise(((t,i)=>{let s=[];e.on("error",i).on("data",(e=>s.push(e))).on("end",(()=>t(Buffer.concat(s).toString())))}))}}}function q(e,t){t.writeHead(e.status,e.headers),t.end(e.text)}function Y(e){return{status:404,headers:{"content-type":"application/json"},text:JSON.stringify({error:`Unknown route: ${e.method} ${e.url}`})}}function W(e,t){q(Y(H(e)),t)}function z(e,{pathPrefix:t,onUnhandledRequest:i}={}){return i&&e.octokit.log.warn("[@octokit/oauth-app] `onUnhandledRequest` is deprecated and will be removed from the next major version."),i??(i=W),async function(s,r,n){const a=H(s),o=await J(e,{pathPrefix:t},a);o?q(o,r):"function"==typeof n?n():i(s,r)}}function $(e){const t=Object.fromEntries(e.headers.entries());return{method:e.method,url:e.url,headers:t,text:()=>e.text()}}function X(e){return new Response(e.text,{status:e.status,headers:e.headers})}async function K(e){return X(Y($(e)))}function Z(e,{pathPrefix:t,onUnhandledRequest:i}={}){return i&&e.octokit.log.warn("[@octokit/oauth-app] `onUnhandledRequest` is deprecated and will be removed from the next major version."),i??(i=K),async function(s){const r=$(s),n=await J(e,{pathPrefix:t},r);return n?X(n):await i(s)}}function ee(...e){return e[0].octokit.log.warn("[@octokit/oauth-app] `createCloudflareHandler` is deprecated, use `createWebWorkerHandler` instead"),Z(...e)}function te(e){const{method:t}=e.requestContext.http;let i=e.rawPath;const{stage:s}=e.requestContext;return i.startsWith("/"+s)&&(i=i.substring(s.length+1)),e.rawQueryString&&(i+="?"+e.rawQueryString),{method:t,url:i,headers:e.headers,text:async()=>e.body||""}}function ie(e){return{statusCode:e.status,headers:e.headers,body:e.text}}async function se(e){return ie(Y(te(e)))}function re(e,{pathPrefix:t,onUnhandledRequest:i}={}){return i&&e.octokit.log.warn("[@octokit/oauth-app] `onUnhandledRequest` is deprecated and will be removed from the next major version."),i??(i=se),async function(s){const r=te(s),n=await J(e,{pathPrefix:t},r);return n?ie(n):i(s)}}var ne=class{static defaults(e){return class extends(this){constructor(...t){super({...e,...t[0]})}}}constructor(e){const t=e.Octokit||E;this.type=e.clientType||"oauth-app";const i=new t({authStrategy:d.createOAuthAppAuth,auth:{clientType:this.type,clientId:e.clientId,clientSecret:e.clientSecret}}),s={clientType:this.type,clientId:e.clientId,clientSecret:e.clientSecret,defaultScopes:e.defaultScopes||[],allowSignup:e.allowSignup,baseUrl:e.baseUrl,redirectUrl:e.redirectUrl,log:e.log,Octokit:t,octokit:i,eventHandlers:{}};this.on=m.bind(null,s),this.octokit=i,this.getUserOctokit=v.bind(null,s),this.getWebFlowAuthorizationUrl=I.bind(null,s),this.createToken=b.bind(null,s),this.checkToken=x.bind(null,s),this.resetToken=S.bind(null,s),this.refreshToken=T.bind(null,s),this.scopeToken=L.bind(null,s),this.deleteToken=U.bind(null,s),this.deleteAuthorization=j.bind(null,s)}};ne.VERSION=h},57633:(e,t,i)=>{"use strict";i.d(t,{a:()=>a});var s=i(85111),r=i(96756),n=i.n(r);async function a(e){const t=e.request||s.W,i=await t("POST /applications/{client_id}/token",{headers:{authorization:`basic ${n()(`${e.clientId}:${e.clientSecret}`)}`},client_id:e.clientId,access_token:e.token}),r={clientType:e.clientType,clientId:e.clientId,clientSecret:e.clientSecret,token:e.token,scopes:i.data.scopes};return i.data.expires_at&&(r.expiresAt=i.data.expires_at),"github-app"===e.clientType&&delete r.scopes,{...i,authentication:r}}},67445:(e,t,i)=>{"use strict";i.d(t,{T:()=>n});var s=i(85111),r=i(57311);async function n(e){const t=e.request||s.W,i={client_id:e.clientId};return"scopes"in e&&Array.isArray(e.scopes)&&(i.scope=e.scopes.join(" ")),(0,r.d)(t,"POST /login/device/code",i)}},20217:(e,t,i)=>{"use strict";i.d(t,{s:()=>a});var s=i(85111),r=i(96756),n=i.n(r);async function a(e){return(e.request||s.W)("DELETE /applications/{client_id}/grant",{headers:{authorization:`basic ${n()(`${e.clientId}:${e.clientSecret}`)}`},client_id:e.clientId,access_token:e.token})}},83818:(e,t,i)=>{"use strict";i.d(t,{p:()=>a});var s=i(85111),r=i(96756),n=i.n(r);async function a(e){return(e.request||s.W)("DELETE /applications/{client_id}/token",{headers:{authorization:`basic ${n()(`${e.clientId}:${e.clientSecret}`)}`},client_id:e.clientId,access_token:e.token})}},16930:(e,t,i)=>{"use strict";i.d(t,{i:()=>n});var s=i(85111),r=i(57311);async function n(e){const t=e.request||s.W,i=await(0,r.d)(t,"POST /login/oauth/access_token",{client_id:e.clientId,device_code:e.code,grant_type:"urn:ietf:params:oauth:grant-type:device_code"}),n={clientType:e.clientType,clientId:e.clientId,token:i.data.access_token,scopes:i.data.scope.split(/\s+/).filter(Boolean)};if("clientSecret"in e&&(n.clientSecret=e.clientSecret),"github-app"===e.clientType){if("refresh_token"in i.data){const e=new Date(i.headers.date).getTime();n.refreshToken=i.data.refresh_token,n.expiresAt=a(e,i.data.expires_in),n.refreshTokenExpiresAt=a(e,i.data.refresh_token_expires_in)}delete n.scopes}return{...i,authentication:n}}function a(e,t){return new Date(e+1e3*t).toISOString()}},90347:(e,t,i)=>{"use strict";i.d(t,{y:()=>n});var s=i(85111),r=i(57311);async function n(e){const t=e.request||s.W,i=await(0,r.d)(t,"POST /login/oauth/access_token",{client_id:e.clientId,client_secret:e.clientSecret,code:e.code,redirect_uri:e.redirectUrl}),n={clientType:e.clientType,clientId:e.clientId,clientSecret:e.clientSecret,token:i.data.access_token,scopes:i.data.scope.split(/\s+/).filter(Boolean)};if("github-app"===e.clientType){if("refresh_token"in i.data){const e=new Date(i.headers.date).getTime();n.refreshToken=i.data.refresh_token,n.expiresAt=a(e,i.data.expires_in),n.refreshTokenExpiresAt=a(e,i.data.refresh_token_expires_in)}delete n.scopes}return{...i,authentication:n}}function a(e,t){return new Date(e+1e3*t).toISOString()}},26925:(e,t,i)=>{"use strict";i.r(t),i.d(t,{VERSION:()=>s,checkToken:()=>p.a,createDeviceCode:()=>c.T,deleteAuthorization:()=>f.s,deleteToken:()=>g.p,exchangeDeviceCode:()=>l.i,exchangeWebFlowCode:()=>o.y,getWebFlowAuthorizationUrl:()=>a,refreshToken:()=>A.g,resetToken:()=>m.E,scopeToken:()=>h});const s="2.0.6";var r=i(85111),n=i(57311);function a({request:e=r.W,...t}){return function(e){const t=e.clientType||"oauth-app",i=e.baseUrl||"https://github.com",s={clientType:t,allowSignup:!1!==e.allowSignup,clientId:e.clientId,login:e.login||null,redirectUrl:e.redirectUrl||null,state:e.state||Math.random().toString(36).substr(2),url:""};if("oauth-app"===t){const t="scopes"in e?e.scopes:[];s.scopes="string"==typeof t?t.split(/[,\s]+/).filter(Boolean):t}return s.url=function(e,t){const i={allowSignup:"allow_signup",clientId:"client_id",login:"login",redirectUrl:"redirect_uri",scopes:"scope",state:"state"};let s=e;return Object.keys(i).filter((e=>null!==t[e])).filter((e=>"scopes"!==e||"github-app"!==t.clientType&&(!Array.isArray(t[e])||t[e].length>0))).map((e=>[i[e],`${t[e]}`])).forEach((([e,t],i)=>{s+=0===i?"?":"&",s+=`${e}=${encodeURIComponent(t)}`})),s}(`${i}/login/oauth/authorize`,s),s}({...t,baseUrl:(0,n.l)(e)})}var o=i(90347),c=i(67445),l=i(16930),p=i(57633),A=i(88655),u=i(96756),d=i.n(u);async function h(e){const{request:t,clientType:i,clientId:s,clientSecret:n,token:a,...o}=e,c=t||r.W,l=await c("POST /applications/{client_id}/token/scoped",{headers:{authorization:`basic ${d()(`${s}:${n}`)}`},client_id:s,access_token:a,...o}),p=Object.assign({clientType:i,clientId:s,clientSecret:n,token:l.data.token},l.data.expires_at?{expiresAt:l.data.expires_at}:{});return{...l,authentication:p}}var m=i(44373),g=i(83818),f=i(20217)},88655:(e,t,i)=>{"use strict";i.d(t,{g:()=>n});var s=i(85111),r=i(57311);async function n(e){const t=e.request||s.W,i=await(0,r.d)(t,"POST /login/oauth/access_token",{client_id:e.clientId,client_secret:e.clientSecret,grant_type:"refresh_token",refresh_token:e.refreshToken}),n=new Date(i.headers.date).getTime(),o={clientType:"github-app",clientId:e.clientId,clientSecret:e.clientSecret,token:i.data.access_token,refreshToken:i.data.refresh_token,expiresAt:a(n,i.data.expires_in),refreshTokenExpiresAt:a(n,i.data.refresh_token_expires_in)};return{...i,authentication:o}}function a(e,t){return new Date(e+1e3*t).toISOString()}},44373:(e,t,i)=>{"use strict";i.d(t,{E:()=>a});var s=i(85111),r=i(96756),n=i.n(r);async function a(e){const t=e.request||s.W,i=n()(`${e.clientId}:${e.clientSecret}`),r=await t("PATCH /applications/{client_id}/token",{headers:{authorization:`basic ${i}`},client_id:e.clientId,access_token:e.token}),a={clientType:e.clientType,clientId:e.clientId,clientSecret:e.clientSecret,token:r.data.token,scopes:r.data.scopes};return r.data.expires_at&&(a.expiresAt=r.data.expires_at),"github-app"===e.clientType&&delete a.scopes,{...r,authentication:a}}},57311:(e,t,i)=>{"use strict";i.d(t,{d:()=>n,l:()=>r});var s=i(33755);function r(e){const t=e.endpoint.DEFAULTS;return/^https:\/\/(api\.)?github\.com$/.test(t.baseUrl)?"https://github.com":t.baseUrl.replace("/api/v3","")}async function n(e,t,i){const n={baseUrl:r(e),headers:{accept:"application/json"},...i},a=await e(t,n);if("error"in a.data){const i=new s.L(`${a.data.error_description} (${a.data.error}, ${a.data.error_uri})`,400,{request:e.endpoint.merge(t,n),headers:a.headers});throw i.response=a,i}return a}},33755:(e,t,i)=>{"use strict";i.d(t,{L:()=>c});var s=i(57751),r=i(36219),n=i.n(r);const a=n()((e=>console.warn(e))),o=n()((e=>console.warn(e)));class c extends Error{constructor(e,t,i){let r;super(e),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name="HttpError",this.status=t,"headers"in i&&void 0!==i.headers&&(r=i.headers),"response"in i&&(this.response=i.response,r=i.response.headers);const n=Object.assign({},i.request);i.request.headers.authorization&&(n.headers=Object.assign({},i.request.headers,{authorization:i.request.headers.authorization.replace(/ .*$/," [REDACTED]")})),n.url=n.url.replace(/\bclient_secret=\w+/g,"client_secret=[REDACTED]").replace(/\baccess_token=\w+/g,"access_token=[REDACTED]"),this.request=n,Object.defineProperty(this,"code",{get:()=>(a(new s.$("[@octokit/request-error] `error.code` is deprecated, use `error.status`.")),t)}),Object.defineProperty(this,"headers",{get:()=>(o(new s.$("[@octokit/request-error] `error.headers` is deprecated, use `error.response.headers`.")),r||{})})}}},85111:(e,t,i)=>{"use strict";i.d(t,{W:()=>B});var s=i(3196);function r(e,t){const i=Object.assign({},e);return Object.keys(t).forEach((n=>{(0,s.P)(t[n])?n in e?i[n]=r(e[n],t[n]):Object.assign(i,{[n]:t[n]}):Object.assign(i,{[n]:t[n]})})),i}function n(e){for(const t in e)void 0===e[t]&&delete e[t];return e}function a(e,t,i){if("string"==typeof t){let[e,s]=t.split(" ");i=Object.assign(s?{method:e,url:s}:{url:e},i)}else i=Object.assign({},t);var s;i.headers=(s=i.headers)?Object.keys(s).reduce(((e,t)=>(e[t.toLowerCase()]=s[t],e)),{}):{},n(i),n(i.headers);const a=r(e||{},i);return e&&e.mediaType.previews.length&&(a.mediaType.previews=e.mediaType.previews.filter((e=>!a.mediaType.previews.includes(e))).concat(a.mediaType.previews)),a.mediaType.previews=a.mediaType.previews.map((e=>e.replace(/-preview/,""))),a}const o=/\{[^}]+\}/g;function c(e){return e.replace(/^\W+|\W+$/g,"").split(/,/)}function l(e,t){return Object.keys(e).filter((e=>!t.includes(e))).reduce(((t,i)=>(t[i]=e[i],t)),{})}function p(e){return e.split(/(%[0-9A-Fa-f]{2})/g).map((function(e){return/%[0-9A-Fa-f]/.test(e)||(e=encodeURI(e).replace(/%5B/g,"[").replace(/%5D/g,"]")),e})).join("")}function A(e){return encodeURIComponent(e).replace(/[!'()*]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}))}function u(e,t,i){return t="+"===e||"#"===e?p(t):A(t),i?A(i)+"="+t:t}function d(e){return null!=e}function h(e){return";"===e||"&"===e||"?"===e}function m(e,t){var i=["+","#",".","/",";","?","&"];return e.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g,(function(e,s,r){if(s){let e="";const r=[];if(-1!==i.indexOf(s.charAt(0))&&(e=s.charAt(0),s=s.substr(1)),s.split(/,/g).forEach((function(i){var s=/([^:\*]*)(?::(\d+)|(\*))?/.exec(i);r.push(function(e,t,i,s){var r=e[i],n=[];if(d(r)&&""!==r)if("string"==typeof r||"number"==typeof r||"boolean"==typeof r)r=r.toString(),s&&"*"!==s&&(r=r.substring(0,parseInt(s,10))),n.push(u(t,r,h(t)?i:""));else if("*"===s)Array.isArray(r)?r.filter(d).forEach((function(e){n.push(u(t,e,h(t)?i:""))})):Object.keys(r).forEach((function(e){d(r[e])&&n.push(u(t,r[e],e))}));else{const e=[];Array.isArray(r)?r.filter(d).forEach((function(i){e.push(u(t,i))})):Object.keys(r).forEach((function(i){d(r[i])&&(e.push(A(i)),e.push(u(t,r[i].toString())))})),h(t)?n.push(A(i)+"="+e.join(",")):0!==e.length&&n.push(e.join(","))}else";"===t?d(r)&&n.push(A(i)):""!==r||"&"!==t&&"?"!==t?""===r&&n.push(""):n.push(A(i)+"=");return n}(t,e,s[1],s[2]||s[3]))})),e&&"+"!==e){var n=",";return"?"===e?n="&":"#"!==e&&(n=e),(0!==r.length?e:"")+r.join(n)}return r.join(",")}return p(r)}))}function g(e){let t,i=e.method.toUpperCase(),s=(e.url||"/").replace(/:([a-z]\w+)/g,"{$1}"),r=Object.assign({},e.headers),n=l(e,["method","baseUrl","url","headers","request","mediaType"]);const a=function(e){const t=e.match(o);return t?t.map(c).reduce(((e,t)=>e.concat(t)),[]):[]}(s);var p;s=(p=s,{expand:m.bind(null,p)}).expand(n),/^http/.test(s)||(s=e.baseUrl+s);const A=l(n,Object.keys(e).filter((e=>a.includes(e))).concat("baseUrl"));if(!/application\/octet-stream/i.test(r.accept)&&(e.mediaType.format&&(r.accept=r.accept.split(/,/).map((t=>t.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/,`application/vnd$1$2.${e.mediaType.format}`))).join(",")),e.mediaType.previews.length)){const t=r.accept.match(/[\w-]+(?=-preview)/g)||[];r.accept=t.concat(e.mediaType.previews).map((t=>`application/vnd.github.${t}-preview${e.mediaType.format?`.${e.mediaType.format}`:"+json"}`)).join(",")}return["GET","HEAD"].includes(i)?s=function(e,t){const i=/\?/.test(e)?"&":"?",s=Object.keys(t);return 0===s.length?e:e+i+s.map((e=>"q"===e?"q="+t.q.split("+").map(encodeURIComponent).join("+"):`${e}=${encodeURIComponent(t[e])}`)).join("&")}(s,A):"data"in A?t=A.data:Object.keys(A).length&&(t=A),r["content-type"]||void 0===t||(r["content-type"]="application/json; charset=utf-8"),["PATCH","PUT"].includes(i)&&void 0===t&&(t=""),Object.assign({method:i,url:s,headers:r},void 0!==t?{body:t}:null,e.request?{request:e.request}:null)}function f(e,t,i){return g(a(e,t,i))}var E=i(21375);const C=function e(t,i){const s=a(t,i),r=f.bind(null,s);return Object.assign(r,{DEFAULTS:s,defaults:e.bind(null,s),merge:a.bind(null,s),parse:g})}(null,{method:"GET",baseUrl:"https://api.github.com",headers:{accept:"application/vnd.github.v3+json","user-agent":`octokit-endpoint.js/7.0.6 ${(0,E.getUserAgent)()}`},mediaType:{format:"",previews:[]}});var y=i(51143),v=i(33755);function w(e){const t=e.request&&e.request.log?e.request.log:console;((0,s.P)(e.body)||Array.isArray(e.body))&&(e.body=JSON.stringify(e.body));let i,r,n={};return(e.request&&e.request.fetch||globalThis.fetch||y.ZP)(e.url,Object.assign({method:e.method,body:e.body,headers:e.headers,redirect:e.redirect,...e.body&&{duplex:"half"}},e.request)).then((async s=>{r=s.url,i=s.status;for(const e of s.headers)n[e[0]]=e[1];if("deprecation"in n){const i=n.link&&n.link.match(/<([^>]+)>; rel="deprecation"/),s=i&&i.pop();t.warn(`[@octokit/request] "${e.method} ${e.url}" is deprecated. It is scheduled to be removed on ${n.sunset}${s?`. See ${s}`:""}`)}if(204!==i&&205!==i){if("HEAD"===e.method){if(i<400)return;throw new v.L(s.statusText,i,{response:{url:r,status:i,headers:n,data:void 0},request:e})}if(304===i)throw new v.L("Not modified",i,{response:{url:r,status:i,headers:n,data:await I(s)},request:e});if(i>=400){const t=await I(s),a=new v.L(function(e){return"string"==typeof e?e:"message"in e?Array.isArray(e.errors)?`${e.message}: ${e.errors.map(JSON.stringify).join(", ")}`:e.message:`Unknown error: ${JSON.stringify(e)}`}(t),i,{response:{url:r,status:i,headers:n,data:t},request:e});throw a}return I(s)}})).then((e=>({status:i,url:r,headers:n,data:e}))).catch((t=>{if(t instanceof v.L)throw t;if("AbortError"===t.name)throw t;throw new v.L(t.message,500,{request:e})}))}async function I(e){const t=e.headers.get("content-type");return/application\/json/.test(t)?e.json():!t||/^text\/|charset=utf-8$/.test(t)?e.text():function(e){return e.arrayBuffer()}(e)}const B=function e(t,i){const s=t.defaults(i);return Object.assign((function(t,i){const r=s.merge(t,i);if(!r.request||!r.request.hook)return w(s.parse(r));const n=(e,t)=>w(s.parse(s.merge(e,t)));return Object.assign(n,{endpoint:s,defaults:e.bind(null,s)}),r.request.hook(n,r)}),{endpoint:s,defaults:e.bind(null,s)})}(C,{headers:{"user-agent":`octokit-request.js/6.2.8 ${(0,E.getUserAgent)()}`}})},94832:(e,t)=>{"use strict";var i;Object.defineProperty(t,"__esModule",{value:!0}),t.ConsoleLogger=t.LogLevel=void 0,function(e){e.ERROR="error",e.WARN="warn",e.INFO="info",e.DEBUG="debug"}(i=t.LogLevel||(t.LogLevel={}));class s{constructor(){this.level=i.INFO,this.name=""}getLevel(){return this.level}setLevel(e){this.level=e}setName(e){this.name=e}debug(...e){s.isMoreOrEqualSevere(i.DEBUG,this.level)&&console.debug(s.labels.get(i.DEBUG),this.name,...e)}info(...e){s.isMoreOrEqualSevere(i.INFO,this.level)&&console.info(s.labels.get(i.INFO),this.name,...e)}warn(...e){s.isMoreOrEqualSevere(i.WARN,this.level)&&console.warn(s.labels.get(i.WARN),this.name,...e)}error(...e){s.isMoreOrEqualSevere(i.ERROR,this.level)&&console.error(s.labels.get(i.ERROR),this.name,...e)}static isMoreOrEqualSevere(e,t){return s.severity[e]>=s.severity[t]}}t.ConsoleLogger=s,s.labels=(()=>{const e=Object.entries(i).map((([e,t])=>[t,`[${e}] `]));return new Map(e)})(),s.severity={[i.ERROR]:400,[i.WARN]:300,[i.INFO]:200,[i.DEBUG]:100}},73247:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},69134:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},8702:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},52866:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},91939:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},92523:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},17578:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__exportStar||function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||s(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),r(i(91939),t),r(i(92523),t),r(i(33144),t),r(i(88851),t),r(i(69700),t),r(i(69134),t),r(i(8702),t),r(i(73247),t),r(i(52866),t)},88851:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},33144:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},69700:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},11055:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t},a=this&&this.__await||function(e){return this instanceof a?(this.v=e,this):new a(e)},o=this&&this.__asyncGenerator||function(e,t,i){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var s,r=i.apply(e,t||[]),n=[];return s={},o("next"),o("throw"),o("return"),s[Symbol.asyncIterator]=function(){return this},s;function o(e){r[e]&&(s[e]=function(t){return new Promise((function(i,s){n.push([e,t,i,s])>1||c(e,t)}))})}function c(e,t){try{(i=r[e](t)).value instanceof a?Promise.resolve(i.value.v).then(l,p):A(n[0][2],i)}catch(e){A(n[0][3],e)}var i}function l(e){c("next",e)}function p(e){c("throw",e)}function A(e,t){e(t),n.shift(),n.length&&c(n[0][0],n[0][1])}},c=this&&this.__asyncValues||function(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,i=e[Symbol.asyncIterator];return i?i.call(e):(e="function"==typeof __values?__values(e):e[Symbol.iterator](),t={},s("next"),s("throw"),s("return"),t[Symbol.asyncIterator]=function(){return this},t);function s(i){t[i]=e[i]&&function(t){return new Promise((function(s,r){!function(e,t,i,s){Promise.resolve(s).then((function(t){e({value:t,done:i})}),t)}(s,r,(t=e[i](t)).done,t.value)}))}}},l=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.buildThreadTsWarningMessage=t.WebClient=t.WebClientEvent=void 0;const p=i(63477),A=i(71017),u=l(i(59796)),d=i(73837),h=l(i(42412)),m=l(i(88464)),g=n(i(32656)),f=l(i(34425)),E=l(i(90504)),C=l(i(30547)),y=i(25242),v=i(57390),w=i(47056),I=i(94139),B=i(39847),b=l(i(83313)),Q=i(28666),x=()=>{};var k;!function(e){e.RATE_LIMITED="rate_limited"}(k=t.WebClientEvent||(t.WebClientEvent={}));class D extends y.Methods{constructor(e,{slackApiUrl:t="https://slack.com/api/",logger:i,logLevel:s,maxRequestConcurrency:r=100,retryConfig:n=B.tenRetriesInAboutThirtyMinutes,agent:a,tls:o,timeout:c=0,rejectRateLimitedCalls:l=!1,headers:p={},teamId:A}={}){super(),this.token=e,this.slackApiUrl=t,this.retryConfig=n,this.requestQueue=new m.default({concurrency:r}),this.tlsConfig=void 0!==o?o:{},this.rejectRateLimitedCalls=l,this.teamId=A,void 0!==i?(this.logger=i,void 0!==s&&this.logger.debug("The logLevel given to WebClient was ignored as you also gave logger")):this.logger=(0,I.getLogger)(D.loggerName,null!=s?s:I.LogLevel.INFO,i),this.token&&!p.Authorization&&(p.Authorization=`Bearer ${this.token}`),this.axios=f.default.create({timeout:c,baseURL:t,headers:(0,C.default)()?p:Object.assign({"User-Agent":(0,v.getUserAgent)()},p),httpAgent:a,httpsAgent:a,transformRequest:[this.serializeApiCallOptions.bind(this)],validateStatus:()=>!0,maxRedirects:0,proxy:!1}),delete this.axios.defaults.headers.post["Content-Type"],this.logger.debug("initialized")}async apiCall(e,t={}){if(this.logger.debug(`apiCall('${e}') start`),function(e,t){const i=["channels.","groups.","im.","mpim."].some((t=>new RegExp(`^${t}`).test(e))),s=["admin.conversations.whitelist.","stars."].some((t=>new RegExp(`^${t}`).test(e)));i?t.warn(`${e} is deprecated. Please use the Conversations API instead. For more info, go to https://api.slack.com/changelog/2020-01-deprecating-antecedents-to-the-conversations-api`):s&&t.warn(`${e} is deprecated. Please check on https://api.slack.com/methods for an alternative.`)}(e,this.logger),function(e,t,i){const s=e=>void 0===e.text||null===e.text||""===e.text,r=()=>`The top-level \`text\` argument is missing in the request payload for a ${e} call - It's a best practice to always provide a \`text\` argument when posting a message. The \`text\` is used in places where the content cannot be rendered such as: system push notifications, assistive technology such as screen readers, etc.`;var n;["chat.postEphemeral","chat.postMessage","chat.scheduleMessage","chat.update"].includes(e)&&"object"==typeof i&&(n=i,Array.isArray(n.attachments)&&n.attachments.length?(e=>Array.isArray(e.attachments)&&e.attachments.some((e=>!e.fallback||""===e.fallback.trim())))(i)&&s(i)&&(t.warn(r()),t.warn(`Additionally, the attachment-level \`fallback\` argument is missing in the request payload for a ${e} call - To avoid this warning, it is recommended to always provide a top-level \`text\` argument when posting a message. Alternatively, you can provide an attachment-level \`fallback\` argument, though this is now considered a legacy field (see https://api.slack.com/reference/messaging/attachments#legacy_fields for more details).`)):s(i)&&t.warn(r()))}(e,this.logger,t),function(e,t,i){["chat.postEphemeral","chat.postMessage","chat.scheduleMessage","files.upload"].includes(e)&&void 0!==(null==i?void 0:i.thread_ts)&&"string"!=typeof(null==i?void 0:i.thread_ts)&&t.warn(R(e))}(e,this.logger,t),"string"==typeof t||"number"==typeof t||"boolean"==typeof t)throw new TypeError("Expected an options argument but instead received a "+typeof t);if((0,Q.warnIfNotUsingFilesUploadV2)(e,this.logger),"files.uploadV2"===e)return this.filesUploadV2(t);const i={};t.token&&(i.Authorization=`Bearer ${t.token}`);const s=await this.makeRequest(e,Object.assign({team_id:this.teamId},t),i),r=await this.buildResult(s);if(this.logger.debug(`http request result: ${JSON.stringify(r)}`),void 0!==r.response_metadata&&void 0!==r.response_metadata.warnings&&r.response_metadata.warnings.forEach(this.logger.warn.bind(this.logger)),void 0!==r.response_metadata&&void 0!==r.response_metadata.messages&&r.response_metadata.messages.forEach((e=>{const t=/\[ERROR\](.*)/,i=/\[WARN\](.*)/;if(t.test(e)){const i=e.match(t);null!=i&&this.logger.error(i[1].trim())}else if(i.test(e)){const t=e.match(i);null!=t&&this.logger.warn(t[1].trim())}})),!r.ok&&"application/gzip"!==s.headers["content-type"])throw(0,w.platformErrorFromResult)(r);if("ok"in r&&!1===r.ok)throw(0,w.platformErrorFromResult)(r);return this.logger.debug(`apiCall('${e}') end`),r}paginate(e,t,i,s){y.cursorPaginationEnabledMethods.has(e)||this.logger.warn(`paginate() called with method ${e}, which is not known to be cursor pagination enabled.`);const r=(()=>{if(void 0!==t&&"number"==typeof t.limit){const{limit:e}=t;return delete t.limit,e}return 200})();function n(){return o(this,arguments,(function*(){let i,s={limit:r};for(void 0!==t&&void 0!==t.cursor&&(s.cursor=t.cursor);void 0===i||void 0!==s;)i=yield a(this.apiCall(e,Object.assign(void 0!==t?t:{},s))),yield yield a(i),s=S(i,r)}))}if(void 0===i)return n.call(this);const l=void 0!==s?s:x;let p=0;return(async()=>{var e,t,s,r;const a=n.call(this),o=(await a.next(void 0)).value;let A=l(void 0,o,p);if(p+=1,i(o))return A;try{for(var u,d=!0,h=c(a);!(e=(u=await h.next()).done);){r=u.value,d=!1;try{const e=r;if(A=l(A,e,p),i(e))return A;p+=1}finally{d=!0}}}catch(e){t={error:e}}finally{try{d||e||!(s=h.return)||await s.call(h)}finally{if(t)throw t.error}}return A})()}async filesUploadV2(e){this.logger.debug("files.uploadV2() start");const t=await this.getAllFileUploads(e);return(await this.fetchAllUploadURLExternal(t)).forEach(((e,i)=>{t[i].upload_url=e.upload_url,t[i].file_id=e.file_id})),await this.postFileUploadsToExternalURL(t,e),{ok:!0,files:await this.completeFileUploads(t)}}async fetchAllUploadURLExternal(e){return Promise.all(e.map((e=>{const t={filename:e.filename,length:e.length,alt_text:e.alt_text,snippet_type:e.snippet_type};return this.files.getUploadURLExternal(t)})))}async completeFileUploads(e){const t=Object.values((0,Q.getAllFileUploadsToComplete)(e));return Promise.all(t.map((e=>this.files.completeUploadExternal(e))))}async postFileUploadsToExternalURL(e,t){return Promise.all(e.map((async e=>{const{upload_url:i,file_id:s,filename:r,data:n}=e,a=n;if(i){const e={};t.token&&(e.Authorization=`Bearer ${t.token}`);const n=await this.makeRequest(i,{body:a},e);if(200!==n.status)return Promise.reject(Error(`Failed to upload file (id:${s}, filename: ${r})`));const o={ok:!0,body:n.data};return Promise.resolve(o)}return Promise.reject(Error(`No upload url found for file (id: ${s}, filename: ${r}`))})))}async getAllFileUploads(e){let t=[];return(e.file||e.content)&&t.push(await(0,Q.getFileUploadJob)(e,this.logger)),e.file_uploads&&(t=t.concat(await(0,Q.getMultipleFileUploadJobs)(e,this.logger))),t}async makeRequest(e,t,i={}){return(0,g.default)((()=>this.requestQueue.add((async()=>{const s=e.startsWith("https")?e:`${this.axios.getUri()+e}`;this.logger.debug(`http request url: ${s}`),this.logger.debug(`http request body: ${JSON.stringify(T(t))}`),this.logger.debug(`http request headers: ${JSON.stringify(T(i))}`);try{const s=Object.assign({headers:i},this.tlsConfig);e.endsWith("admin.analytics.getFile")&&(s.responseType="arraybuffer");const r=await this.axios.post(e,t,s);if(this.logger.debug("http response received"),429===r.status){const i=_(r);if(void 0!==i){if(this.emit(k.RATE_LIMITED,i,{url:e,body:t}),this.rejectRateLimitedCalls)throw new g.AbortError((0,w.rateLimitedErrorWithDelay)(i));throw this.logger.info(`API Call failed due to rate limiting. Will retry in ${i} seconds.`),this.requestQueue.pause(),await(0,b.default)(1e3*i),this.requestQueue.start(),Error(`A rate limit was exceeded (url: ${e}, retry-after: ${i})`)}throw new g.AbortError(new Error(`Retry header did not contain a valid timeout (url: ${e}, retry-after header: ${r.headers["retry-after"]})`))}if(200!==r.status)throw(0,w.httpErrorFromResponse)(r);return r}catch(e){const t=e;if(this.logger.warn("http request failed",t.message),t.request)throw(0,w.requestErrorWithOriginal)(t);throw e}}))),this.retryConfig)}serializeApiCallOptions(e,t){let i=!1;const s=Object.entries(e).map((([e,t])=>{if(null==t)return[];let s=t;return Buffer.isBuffer(t)||(0,h.default)(t)?i=!0:"string"!=typeof t&&"number"!=typeof t&&"boolean"!=typeof t&&(s=JSON.stringify(t)),[e,s]}));if(i){this.logger.debug("Request arguments contain binary data");const e=s.reduce(((e,[t,i])=>{if(Buffer.isBuffer(i)||(0,h.default)(i)){const s={};s.filename=(()=>{const e=i;return"string"==typeof e.name?(0,A.basename)(e.name):"string"==typeof e.path?(0,A.basename)(e.path):"Untitled"})(),e.append(t,i,s)}else void 0!==t&&void 0!==i&&e.append(t,i);return e}),new E.default);return Object.entries(e.getHeaders()).forEach((([e,i])=>{t[e]=i})),e}return t["Content-Type"]="application/x-www-form-urlencoded",(0,p.stringify)(s.reduce(((e,[t,i])=>(void 0!==t&&void 0!==i&&(e[t]=i),e)),{}))}async buildResult(e){let{data:t}=e;const i="application/gzip"===e.headers["content-type"];if(i)try{const e=await new Promise(((e,i)=>{u.default.unzip(t,((t,s)=>t?i(t):e(s.toString().split("\n"))))})).then((e=>e)).catch((e=>{throw e})),i=[];Array.isArray(e)&&e.forEach((e=>{e&&e.length>0&&i.push(JSON.parse(e))})),t={file_data:i}}catch(e){t={ok:!1,error:e}}else i||"/api/admin.analytics.getFile"!==e.request.path||(t=JSON.parse((new d.TextDecoder).decode(t)));if("string"==typeof t)try{t=JSON.parse(t)}catch(e){t={ok:!1,error:t}}void 0===t.response_metadata&&(t.response_metadata={}),void 0!==e.headers["x-oauth-scopes"]&&(t.response_metadata.scopes=e.headers["x-oauth-scopes"].trim().split(/\s*,\s*/)),void 0!==e.headers["x-accepted-oauth-scopes"]&&(t.response_metadata.acceptedScopes=e.headers["x-accepted-oauth-scopes"].trim().split(/\s*,\s*/));const s=_(e);return void 0!==s&&(t.response_metadata.retryAfter=s),t}}function S(e,t){if(void 0!==e&&void 0!==e.response_metadata&&void 0!==e.response_metadata.next_cursor&&""!==e.response_metadata.next_cursor)return{limit:t,cursor:e.response_metadata.next_cursor}}function _(e){if(void 0!==e.headers["retry-after"]){const t=parseInt(e.headers["retry-after"],10);if(!Number.isNaN(t))return t}}function R(e){return`The given thread_ts value in the request payload for a ${e} call is a float value. We highly recommend using a string value instead.`}function T(e){return Object.entries(e).map((([e,t])=>{if(null==t)return[];let i=t;return(null!==e.match(/.*token.*/)||e.match(/[Aa]uthorization/))&&(i="[[REDACTED]]"),Buffer.isBuffer(t)||(0,h.default)(t)?i="[[BINARY VALUE OMITTED]]":"string"!=typeof t&&"number"!=typeof t&&"boolean"!=typeof t&&(i=JSON.stringify(t)),[e,i]})).reduce(((e,[t,i])=>(void 0!==t&&void 0!==i&&(e[t]=i),e)),{})}t.WebClient=D,D.loggerName="WebClient",t.default=D,t.buildThreadTsWarningMessage=R},47056:(e,t)=>{"use strict";var i;function s(e,t){const i=e;return i.code=t,i}Object.defineProperty(t,"__esModule",{value:!0}),t.rateLimitedErrorWithDelay=t.platformErrorFromResult=t.httpErrorFromResponse=t.requestErrorWithOriginal=t.errorWithCode=t.ErrorCode=void 0,function(e){e.RequestError="slack_webapi_request_error",e.HTTPError="slack_webapi_http_error",e.PlatformError="slack_webapi_platform_error",e.RateLimitedError="slack_webapi_rate_limited_error",e.FileUploadInvalidArgumentsError="slack_webapi_file_upload_invalid_args_error",e.FileUploadReadFileDataError="slack_webapi_file_upload_read_file_data_error"}(i=t.ErrorCode||(t.ErrorCode={})),t.errorWithCode=s,t.requestErrorWithOriginal=function(e){const t=s(new Error(`A request error occurred: ${e.message}`),i.RequestError);return t.original=e,t},t.httpErrorFromResponse=function(e){const t=s(new Error(`An HTTP protocol error occurred: statusCode = ${e.status}`),i.HTTPError);t.statusCode=e.status,t.statusMessage=e.statusText;const r={};return Object.keys(e.headers).forEach((t=>{t&&e.headers[t]&&(r[t]=e.headers[t])})),t.headers=r,t.body=e.data,t},t.platformErrorFromResult=function(e){const t=s(new Error(`An API error occurred: ${e.error}`),i.PlatformError);return t.data=e,t},t.rateLimitedErrorWithDelay=function(e){const t=s(new Error(`A rate-limit has been reached, you may retry this request in ${e} seconds`),i.RateLimitedError);return t.retryAfter=e,t}},28666:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.buildInvalidFilesUploadParamError=t.buildMultipleChannelsErrorMsg=t.buildChannelsWarning=t.buildFilesUploadMissingMessage=t.buildGeneralFilesUploadWarning=t.buildLegacyMethodWarning=t.buildMissingExtensionWarning=t.buildMissingFileNameWarning=t.buildLegacyFileTypeWarning=t.buildFileSizeErrorMsg=t.buildMissingFileIdError=t.warnIfLegacyFileType=t.warnIfMissingOrInvalidFileNameAndDefault=t.errorIfInvalidOrMissingFileData=t.errorIfChannelsCsv=t.warnIfChannels=t.warnIfNotUsingFilesUploadV2=t.getAllFileUploadsToComplete=t.getFileDataAsStream=t.getFileDataLength=t.getFileData=t.getMultipleFileUploadJobs=t.getFileUploadJob=void 0;const s=i(57147),r=i(12781),n=i(47056);async function a(e,t){var i,s,r,n;h(e,t),p(e,t),A(e);const a=d(e,t),l=await o(e),u=c(l);return{alt_text:e.alt_text,channel_id:null!==(i=e.channels)&&void 0!==i?i:e.channel_id,content:e.content,file:e.file,filename:null!==(s=e.filename)&&void 0!==s?s:a,initial_comment:e.initial_comment,snippet_type:e.snippet_type,thread_ts:e.thread_ts,title:null!==(r=e.title)&&void 0!==r?r:null!==(n=e.filename)&&void 0!==n?n:a,data:l,length:u}}async function o(e){u(e);const{file:t,content:i}=e;if(t){if(Buffer.isBuffer(t))return t;if("string"==typeof t)try{return(0,s.readFileSync)(t)}catch(e){throw(0,n.errorWithCode)(new Error(`Unable to resolve file data for ${t}. Please supply a filepath string, or binary data Buffer or String directly.`),n.ErrorCode.FileUploadInvalidArgumentsError)}const e=await l(t);if(e)return e}if(i)return Buffer.from(i);throw(0,n.errorWithCode)(new Error("There was an issue getting the file data for the file or content supplied"),n.ErrorCode.FileUploadReadFileDataError)}function c(e){if(e)return Buffer.byteLength(e,"utf8");throw(0,n.errorWithCode)(new Error("There was an issue calculating the size of your file"),n.ErrorCode.FileUploadReadFileDataError)}async function l(e){const t=[];return new Promise(((i,s)=>{e.on("readable",(()=>{let i;for(;null!==(i=e.read());)t.push(i)})),e.on("end",(()=>{if(t.length>0){const e=Buffer.concat(t);i(e)}else s(Error("No data in supplied file"))}))}))}function p(e,t){e.channels&&t.warn("Although the 'channels' parameter is still supported for smoother migration from legacy files.upload, we recommend using the new channel_id parameter with a single str value instead (e.g. 'C12345').")}function A(e){if((e.channels?e.channels.split(","):[]).length>1)throw(0,n.errorWithCode)(new Error("Sharing files with multiple channels is no longer supported in v2. Share files in each channel separately instead."),n.ErrorCode.FileUploadInvalidArgumentsError)}function u(e){const{file:t,content:i}=e;if(!t&&!i||t&&i)throw(0,n.errorWithCode)(new Error("Either a file or content field is required for valid file upload. You cannot supply both"),n.ErrorCode.FileUploadInvalidArgumentsError);if(t&&!("string"==typeof t||Buffer.isBuffer(t)||t instanceof r.Readable))throw(0,n.errorWithCode)(new Error("file must be a valid string path, buffer or Readable"),n.ErrorCode.FileUploadInvalidArgumentsError);if(i&&"string"!=typeof i)throw(0,n.errorWithCode)(new Error("content must be a string"),n.ErrorCode.FileUploadInvalidArgumentsError)}function d(e,t){var i;const s=`file.${null!==(i=e.filetype)&&void 0!==i?i:"txt"}`,{filename:r}=e;return r?(r.split(".").length<2&&t.warn(m(r)),r):(t.warn("filename is a required field for files.uploadV2. \n For backwards compatibility and ease of migration, defaulting the filename. For best experience and consistent unfurl behavior, you should set the filename property with correct file extension, e.g. image.png, text.txt"),s)}function h(e,t){e.filetype&&t.warn("filetype is no longer a supported field in files.uploadV2. \nPlease remove this field. To indicate file type, please do so via the required filename property using the appropriate file extension, e.g. image.png, text.txt")}function m(e){return`filename supplied '${e}' may be missing a proper extension. Missing extenions may result in unexpected unfurl behavior when shared`}function g(e){return`${e} may cause some issues like timeouts for relatively large files.`}t.getFileUploadJob=a,t.getMultipleFileUploadJobs=async function(e,t){if(e.file_uploads)return Promise.all(e.file_uploads.map((i=>{const{channel_id:s,channels:r,initial_comment:o,thread_ts:c}=i;if(s||r||o||c)throw(0,n.errorWithCode)(new Error("You may supply file_uploads only for a single channel, comment, thread respectively. Therefore, please supply any channel_id, initial_comment, thread_ts in the top-layer."),n.ErrorCode.FileUploadInvalidArgumentsError);return a(Object.assign(Object.assign({},i),{channels:e.channels,channel_id:e.channel_id,initial_comment:e.initial_comment,thread_ts:e.thread_ts}),t)})));throw new Error("Something went wrong with processing file_uploads")},t.getFileData=o,t.getFileDataLength=c,t.getFileDataAsStream=l,t.getAllFileUploadsToComplete=function(e){const t={};return e.forEach((e=>{const{channel_id:i,thread_ts:s,initial_comment:r,file_id:n,title:a}=e;if(!n)throw new Error("Missing required file id for file upload completion");{const e=`:::${i}:::${s}:::${r}`;Object.prototype.hasOwnProperty.call(t,e)?t[e].files.push({id:n,title:a}):t[e]={files:[{id:n,title:a}],channel_id:i,initial_comment:r,thread_ts:s}}})),t},t.warnIfNotUsingFilesUploadV2=function(e,t){const i=["files.upload"].includes(e);"files.upload"===e&&t.warn(g(e)),i&&t.info("Our latest recommendation is to use client.files.uploadV2() method, which is mostly compatible and much stabler, instead.")},t.warnIfChannels=p,t.errorIfChannelsCsv=A,t.errorIfInvalidOrMissingFileData=u,t.warnIfMissingOrInvalidFileNameAndDefault=d,t.warnIfLegacyFileType=h,t.buildMissingFileIdError=function(){return"Missing required file id for file upload completion"},t.buildFileSizeErrorMsg=function(){return"There was an issue calculating the size of your file"},t.buildLegacyFileTypeWarning=function(){return"filetype is no longer a supported field in files.uploadV2. \nPlease remove this field. To indicate file type, please do so via the required filename property using the appropriate file extension, e.g. image.png, text.txt"},t.buildMissingFileNameWarning=function(){return"filename is a required field for files.uploadV2. \n For backwards compatibility and ease of migration, defaulting the filename. For best experience and consistent unfurl behavior, you should set the filename property with correct file extension, e.g. image.png, text.txt"},t.buildMissingExtensionWarning=m,t.buildLegacyMethodWarning=g,t.buildGeneralFilesUploadWarning=function(){return"Our latest recommendation is to use client.files.uploadV2() method, which is mostly compatible and much stabler, instead."},t.buildFilesUploadMissingMessage=function(){return"Something went wrong with processing file_uploads"},t.buildChannelsWarning=function(){return"Although the 'channels' parameter is still supported for smoother migration from legacy files.upload, we recommend using the new channel_id parameter with a single str value instead (e.g. 'C12345')."},t.buildMultipleChannelsErrorMsg=function(){return"Sharing files with multiple channels is no longer supported in v2. Share files in each channel separately instead."},t.buildInvalidFilesUploadParamError=function(){return"You may supply file_uploads only for a single channel, comment, thread respectively. Therefore, please supply any channel_id, initial_comment, thread_ts in the top-layer."}},83313:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){return new Promise((t=>{setTimeout(t,e)}))}},85314:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__exportStar||function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||s(t,e,i)},n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.addAppMetadata=t.retryPolicies=t.ErrorCode=t.LogLevel=t.WebClientEvent=t.WebClient=void 0;var a=i(11055);Object.defineProperty(t,"WebClient",{enumerable:!0,get:function(){return a.WebClient}}),Object.defineProperty(t,"WebClientEvent",{enumerable:!0,get:function(){return a.WebClientEvent}});var o=i(94139);Object.defineProperty(t,"LogLevel",{enumerable:!0,get:function(){return o.LogLevel}});var c=i(47056);Object.defineProperty(t,"ErrorCode",{enumerable:!0,get:function(){return c.ErrorCode}});var l=i(39847);Object.defineProperty(t,"retryPolicies",{enumerable:!0,get:function(){return n(l).default}});var p=i(57390);Object.defineProperty(t,"addAppMetadata",{enumerable:!0,get:function(){return p.addAppMetadata}}),r(i(25242),t),r(i(82356),t)},57390:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.getUserAgent=t.addAppMetadata=void 0;const a=n(i(22037)),o=i(71017),c=i(10191);function l(e){return e.replace("/",":")}const p=`${l(c.name)}/${c.version} ${(0,o.basename)(process.title)}/${process.version.replace("v","")} ${a.platform()}/${a.release()}`,A={};t.addAppMetadata=function({name:e,version:t}){A[l(e)]=t},t.getUserAgent=function(){const e=Object.entries(A).map((([e,t])=>`${e}/${t}`)).join(" ");return(e.length>0?`${e} `:"")+p}},94139:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getLogger=t.LogLevel=void 0;const s=i(94832);var r=i(94832);Object.defineProperty(t,"LogLevel",{enumerable:!0,get:function(){return r.LogLevel}});let n=0;t.getLogger=function(e,t,i){const r=n;n+=1;const a=void 0!==i?i:new s.ConsoleLogger;return a.setName(`web-api:${e}:${r}`),void 0!==t&&a.setLevel(t),a}},25242:function(e,t,i){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__exportStar||function(e,t){for(var i in e)"default"===i||Object.prototype.hasOwnProperty.call(t,i)||s(t,e,i)};Object.defineProperty(t,"__esModule",{value:!0}),t.cursorPaginationEnabledMethods=t.Methods=void 0;const n=i(91906),a=i(11055);function o(e,t){return e.apiCall.bind(e,t)}function c(e){return e.filesUploadV2.bind(e)}class l extends n.EventEmitter{constructor(){if(super(),this.admin={analytics:{getFile:o(this,"admin.analytics.getFile")},apps:{approve:o(this,"admin.apps.approve"),approved:{list:o(this,"admin.apps.approved.list")},clearResolution:o(this,"admin.apps.clearResolution"),requests:{cancel:o(this,"admin.apps.requests.cancel"),list:o(this,"admin.apps.requests.list")},restrict:o(this,"admin.apps.restrict"),restricted:{list:o(this,"admin.apps.restricted.list")},uninstall:o(this,"admin.apps.uninstall"),activities:{list:o(this,"admin.apps.activities.list")}},auth:{policy:{assignEntities:o(this,"admin.auth.policy.assignEntities"),getEntities:o(this,"admin.auth.policy.getEntities"),removeEntities:o(this,"admin.auth.policy.removeEntities")}},barriers:{create:o(this,"admin.barriers.create"),delete:o(this,"admin.barriers.delete"),list:o(this,"admin.barriers.list"),update:o(this,"admin.barriers.update")},conversations:{archive:o(this,"admin.conversations.archive"),bulkArchive:o(this,"admin.conversations.bulkArchive"),bulkDelete:o(this,"admin.conversations.bulkDelete"),bulkMove:o(this,"admin.conversations.bulkMove"),convertToPrivate:o(this,"admin.conversations.convertToPrivate"),convertToPublic:o(this,"admin.conversations.convertToPublic"),create:o(this,"admin.conversations.create"),delete:o(this,"admin.conversations.delete"),disconnectShared:o(this,"admin.conversations.disconnectShared"),ekm:{listOriginalConnectedChannelInfo:o(this,"admin.conversations.ekm.listOriginalConnectedChannelInfo")},getConversationPrefs:o(this,"admin.conversations.getConversationPrefs"),getTeams:o(this,"admin.conversations.getTeams"),invite:o(this,"admin.conversations.invite"),rename:o(this,"admin.conversations.rename"),restrictAccess:{addGroup:o(this,"admin.conversations.restrictAccess.addGroup"),listGroups:o(this,"admin.conversations.restrictAccess.listGroups"),removeGroup:o(this,"admin.conversations.restrictAccess.removeGroup")},getCustomRetention:o(this,"admin.conversations.getCustomRetention"),setCustomRetention:o(this,"admin.conversations.setCustomRetention"),removeCustomRetention:o(this,"admin.conversations.removeCustomRetention"),lookup:o(this,"admin.conversations.lookup"),search:o(this,"admin.conversations.search"),setConversationPrefs:o(this,"admin.conversations.setConversationPrefs"),setTeams:o(this,"admin.conversations.setTeams"),unarchive:o(this,"admin.conversations.unarchive")},emoji:{add:o(this,"admin.emoji.add"),addAlias:o(this,"admin.emoji.addAlias"),list:o(this,"admin.emoji.list"),remove:o(this,"admin.emoji.remove"),rename:o(this,"admin.emoji.rename")},functions:{list:o(this,"admin.functions.list"),permissions:{lookup:o(this,"admin.functions.permissions.lookup"),set:o(this,"admin.functions.permissions.set")}},inviteRequests:{approve:o(this,"admin.inviteRequests.approve"),approved:{list:o(this,"admin.inviteRequests.approved.list")},denied:{list:o(this,"admin.inviteRequests.denied.list")},deny:o(this,"admin.inviteRequests.deny"),list:o(this,"admin.inviteRequests.list")},teams:{admins:{list:o(this,"admin.teams.admins.list")},create:o(this,"admin.teams.create"),list:o(this,"admin.teams.list"),owners:{list:o(this,"admin.teams.owners.list")},settings:{info:o(this,"admin.teams.settings.info"),setDefaultChannels:o(this,"admin.teams.settings.setDefaultChannels"),setDescription:o(this,"admin.teams.settings.setDescription"),setDiscoverability:o(this,"admin.teams.settings.setDiscoverability"),setIcon:o(this,"admin.teams.settings.setIcon"),setName:o(this,"admin.teams.settings.setName")}},roles:{addAssignments:o(this,"admin.roles.addAssignments"),listAssignments:o(this,"admin.roles.listAssignments"),removeAssignments:o(this,"admin.roles.removeAssignments")},usergroups:{addChannels:o(this,"admin.usergroups.addChannels"),addTeams:o(this,"admin.usergroups.addTeams"),listChannels:o(this,"admin.usergroups.listChannels"),removeChannels:o(this,"admin.usergroups.removeChannels")},users:{assign:o(this,"admin.users.assign"),invite:o(this,"admin.users.invite"),list:o(this,"admin.users.list"),remove:o(this,"admin.users.remove"),session:{list:o(this,"admin.users.session.list"),reset:o(this,"admin.users.session.reset"),resetBulk:o(this,"admin.users.session.resetBulk"),invalidate:o(this,"admin.users.session.invalidate"),getSettings:o(this,"admin.users.session.getSettings"),setSettings:o(this,"admin.users.session.setSettings"),clearSettings:o(this,"admin.users.session.clearSettings")},unsupportedVersions:{export:o(this,"admin.users.unsupportedVersions.export")},setAdmin:o(this,"admin.users.setAdmin"),setExpiration:o(this,"admin.users.setExpiration"),setOwner:o(this,"admin.users.setOwner"),setRegular:o(this,"admin.users.setRegular")},workflows:{search:o(this,"admin.workflows.search"),unpublish:o(this,"admin.workflows.unpublish"),collaborators:{add:o(this,"admin.workflows.collaborators.add"),remove:o(this,"admin.workflows.collaborators.remove")},permissions:{lookup:o(this,"admin.workflows.permissions.lookup")}}},this.api={test:o(this,"api.test")},this.apps={connections:{open:o(this,"apps.connections.open")},event:{authorizations:{list:o(this,"apps.event.authorizations.list")}},manifest:{create:o(this,"apps.manifest.create"),delete:o(this,"apps.manifest.delete"),export:o(this,"apps.manifest.export"),update:o(this,"apps.manifest.update"),validate:o(this,"apps.manifest.validate")},uninstall:o(this,"apps.uninstall")},this.auth={revoke:o(this,"auth.revoke"),teams:{list:o(this,"auth.teams.list")},test:o(this,"auth.test")},this.bots={info:o(this,"bots.info")},this.bookmarks={add:o(this,"bookmarks.add"),edit:o(this,"bookmarks.edit"),list:o(this,"bookmarks.list"),remove:o(this,"bookmarks.remove")},this.calls={add:o(this,"calls.add"),end:o(this,"calls.end"),info:o(this,"calls.info"),update:o(this,"calls.update"),participants:{add:o(this,"calls.participants.add"),remove:o(this,"calls.participants.remove")}},this.chat={delete:o(this,"chat.delete"),deleteScheduledMessage:o(this,"chat.deleteScheduledMessage"),getPermalink:o(this,"chat.getPermalink"),meMessage:o(this,"chat.meMessage"),postEphemeral:o(this,"chat.postEphemeral"),postMessage:o(this,"chat.postMessage"),scheduleMessage:o(this,"chat.scheduleMessage"),scheduledMessages:{list:o(this,"chat.scheduledMessages.list")},unfurl:o(this,"chat.unfurl"),update:o(this,"chat.update")},this.conversations={acceptSharedInvite:o(this,"conversations.acceptSharedInvite"),approveSharedInvite:o(this,"conversations.approveSharedInvite"),archive:o(this,"conversations.archive"),close:o(this,"conversations.close"),create:o(this,"conversations.create"),declineSharedInvite:o(this,"conversations.declineSharedInvite"),history:o(this,"conversations.history"),info:o(this,"conversations.info"),invite:o(this,"conversations.invite"),inviteShared:o(this,"conversations.inviteShared"),join:o(this,"conversations.join"),kick:o(this,"conversations.kick"),leave:o(this,"conversations.leave"),list:o(this,"conversations.list"),listConnectInvites:o(this,"conversations.listConnectInvites"),mark:o(this,"conversations.mark"),members:o(this,"conversations.members"),open:o(this,"conversations.open"),rename:o(this,"conversations.rename"),replies:o(this,"conversations.replies"),setPurpose:o(this,"conversations.setPurpose"),setTopic:o(this,"conversations.setTopic"),unarchive:o(this,"conversations.unarchive")},this.dialog={open:o(this,"dialog.open")},this.dnd={endDnd:o(this,"dnd.endDnd"),endSnooze:o(this,"dnd.endSnooze"),info:o(this,"dnd.info"),setSnooze:o(this,"dnd.setSnooze"),teamInfo:o(this,"dnd.teamInfo")},this.emoji={list:o(this,"emoji.list")},this.files={delete:o(this,"files.delete"),info:o(this,"files.info"),list:o(this,"files.list"),revokePublicURL:o(this,"files.revokePublicURL"),sharedPublicURL:o(this,"files.sharedPublicURL"),upload:o(this,"files.upload"),uploadV2:c(this),getUploadURLExternal:o(this,"files.getUploadURLExternal"),completeUploadExternal:o(this,"files.completeUploadExternal"),comments:{delete:o(this,"files.comments.delete")},remote:{info:o(this,"files.remote.info"),list:o(this,"files.remote.list"),add:o(this,"files.remote.add"),update:o(this,"files.remote.update"),remove:o(this,"files.remote.remove"),share:o(this,"files.remote.share")}},this.migration={exchange:o(this,"migration.exchange")},this.oauth={access:o(this,"oauth.access"),v2:{access:o(this,"oauth.v2.access"),exchange:o(this,"oauth.v2.exchange")}},this.openid={connect:{token:o(this,"openid.connect.token"),userInfo:o(this,"openid.connect.userInfo")}},this.pins={add:o(this,"pins.add"),list:o(this,"pins.list"),remove:o(this,"pins.remove")},this.reactions={add:o(this,"reactions.add"),get:o(this,"reactions.get"),list:o(this,"reactions.list"),remove:o(this,"reactions.remove")},this.reminders={add:o(this,"reminders.add"),complete:o(this,"reminders.complete"),delete:o(this,"reminders.delete"),info:o(this,"reminders.info"),list:o(this,"reminders.list")},this.rtm={connect:o(this,"rtm.connect"),start:o(this,"rtm.start")},this.search={all:o(this,"search.all"),files:o(this,"search.files"),messages:o(this,"search.messages")},this.stars={add:o(this,"stars.add"),list:o(this,"stars.list"),remove:o(this,"stars.remove")},this.team={accessLogs:o(this,"team.accessLogs"),billableInfo:o(this,"team.billableInfo"),billing:{info:o(this,"team.billing.info")},info:o(this,"team.info"),integrationLogs:o(this,"team.integrationLogs"),preferences:{list:o(this,"team.preferences.list")},profile:{get:o(this,"team.profile.get")}},this.tooling={tokens:{rotate:o(this,"tooling.tokens.rotate")}},this.usergroups={create:o(this,"usergroups.create"),disable:o(this,"usergroups.disable"),enable:o(this,"usergroups.enable"),list:o(this,"usergroups.list"),update:o(this,"usergroups.update"),users:{list:o(this,"usergroups.users.list"),update:o(this,"usergroups.users.update")}},this.users={conversations:o(this,"users.conversations"),deletePhoto:o(this,"users.deletePhoto"),getPresence:o(this,"users.getPresence"),identity:o(this,"users.identity"),info:o(this,"users.info"),list:o(this,"users.list"),lookupByEmail:o(this,"users.lookupByEmail"),setPhoto:o(this,"users.setPhoto"),setPresence:o(this,"users.setPresence"),profile:{get:o(this,"users.profile.get"),set:o(this,"users.profile.set")}},this.views={open:o(this,"views.open"),publish:o(this,"views.publish"),push:o(this,"views.push"),update:o(this,"views.update")},this.workflows={stepCompleted:o(this,"workflows.stepCompleted"),stepFailed:o(this,"workflows.stepFailed"),updateStep:o(this,"workflows.updateStep")},this.channels={archive:o(this,"channels.archive"),create:o(this,"channels.create"),history:o(this,"channels.history"),info:o(this,"channels.info"),invite:o(this,"channels.invite"),join:o(this,"channels.join"),kick:o(this,"channels.kick"),leave:o(this,"channels.leave"),list:o(this,"channels.list"),mark:o(this,"channels.mark"),rename:o(this,"channels.rename"),replies:o(this,"channels.replies"),setPurpose:o(this,"channels.setPurpose"),setTopic:o(this,"channels.setTopic"),unarchive:o(this,"channels.unarchive")},this.groups={archive:o(this,"groups.archive"),create:o(this,"groups.create"),createChild:o(this,"groups.createChild"),history:o(this,"groups.history"),info:o(this,"groups.info"),invite:o(this,"groups.invite"),kick:o(this,"groups.kick"),leave:o(this,"groups.leave"),list:o(this,"groups.list"),mark:o(this,"groups.mark"),open:o(this,"groups.open"),rename:o(this,"groups.rename"),replies:o(this,"groups.replies"),setPurpose:o(this,"groups.setPurpose"),setTopic:o(this,"groups.setTopic"),unarchive:o(this,"groups.unarchive")},this.im={close:o(this,"im.close"),history:o(this,"im.history"),list:o(this,"im.list"),mark:o(this,"im.mark"),open:o(this,"im.open"),replies:o(this,"im.replies")},this.mpim={close:o(this,"mpim.close"),history:o(this,"mpim.history"),list:o(this,"mpim.list"),mark:o(this,"mpim.mark"),open:o(this,"mpim.open"),replies:o(this,"mpim.replies")},new.target!==a.WebClient&&!(new.target.prototype instanceof a.WebClient))throw new Error("Attempt to inherit from WebClient methods without inheriting from WebClient")}}t.Methods=l,t.cursorPaginationEnabledMethods=new Set,t.cursorPaginationEnabledMethods.add("admin.apps.approved.list"),t.cursorPaginationEnabledMethods.add("admin.apps.requests.list"),t.cursorPaginationEnabledMethods.add("admin.apps.restricted.list"),t.cursorPaginationEnabledMethods.add("admin.apps.activities.list"),t.cursorPaginationEnabledMethods.add("admin.auth.policy.getEntities"),t.cursorPaginationEnabledMethods.add("admin.barriers.list"),t.cursorPaginationEnabledMethods.add("admin.conversations.lookup"),t.cursorPaginationEnabledMethods.add("admin.conversations.ekm.listOriginalConnectedChannelInfo"),t.cursorPaginationEnabledMethods.add("admin.conversations.getTeams"),t.cursorPaginationEnabledMethods.add("admin.conversations.search"),t.cursorPaginationEnabledMethods.add("admin.emoji.list"),t.cursorPaginationEnabledMethods.add("admin.inviteRequests.approved.list"),t.cursorPaginationEnabledMethods.add("admin.inviteRequests.denied.list"),t.cursorPaginationEnabledMethods.add("admin.inviteRequests.list"),t.cursorPaginationEnabledMethods.add("admin.roles.listAssignments"),t.cursorPaginationEnabledMethods.add("admin.inviteRequests.list"),t.cursorPaginationEnabledMethods.add("admin.teams.admins.list"),t.cursorPaginationEnabledMethods.add("admin.teams.list"),t.cursorPaginationEnabledMethods.add("admin.teams.owners.list"),t.cursorPaginationEnabledMethods.add("admin.users.list"),t.cursorPaginationEnabledMethods.add("admin.users.session.list"),t.cursorPaginationEnabledMethods.add("admin.worfklows.search"),t.cursorPaginationEnabledMethods.add("apps.event.authorizations.list"),t.cursorPaginationEnabledMethods.add("auth.teams.list"),t.cursorPaginationEnabledMethods.add("channels.list"),t.cursorPaginationEnabledMethods.add("chat.scheduledMessages.list"),t.cursorPaginationEnabledMethods.add("conversations.history"),t.cursorPaginationEnabledMethods.add("conversations.list"),t.cursorPaginationEnabledMethods.add("conversations.listConnectInvites"),t.cursorPaginationEnabledMethods.add("conversations.members"),t.cursorPaginationEnabledMethods.add("conversations.replies"),t.cursorPaginationEnabledMethods.add("files.info"),t.cursorPaginationEnabledMethods.add("files.remote.list"),t.cursorPaginationEnabledMethods.add("groups.list"),t.cursorPaginationEnabledMethods.add("im.list"),t.cursorPaginationEnabledMethods.add("mpim.list"),t.cursorPaginationEnabledMethods.add("reactions.list"),t.cursorPaginationEnabledMethods.add("stars.list"),t.cursorPaginationEnabledMethods.add("team.accessLogs"),t.cursorPaginationEnabledMethods.add("users.conversations"),t.cursorPaginationEnabledMethods.add("users.list"),r(i(17578),t)},82356:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0})},39847:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.rapidRetryPolicy=t.fiveRetriesInFiveMinutes=t.tenRetriesInAboutThirtyMinutes=void 0,t.tenRetriesInAboutThirtyMinutes={retries:10,factor:1.96821,randomize:!0},t.fiveRetriesInFiveMinutes={retries:5,factor:3.86},t.rapidRetryPolicy={minTimeout:0,maxTimeout:1};const i={tenRetriesInAboutThirtyMinutes:t.tenRetriesInAboutThirtyMinutes,fiveRetriesInFiveMinutes:t.fiveRetriesInFiveMinutes,rapidRetryPolicy:t.rapidRetryPolicy};t.default=i},14686:(e,t,i)=>{"use strict";const s=i(96066),r=i(53072);class n extends Error{constructor(e){if(!Array.isArray(e))throw new TypeError("Expected input to be an Array, got "+typeof e);let t=(e=[...e].map((e=>e instanceof Error?e:null!==e&&"object"==typeof e?Object.assign(new Error(e.message),e):new Error(e)))).map((e=>"string"==typeof e.stack?r(e.stack).replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g,""):String(e))).join("\n");t="\n"+s(t,4),super(t),this.name="AggregateError",Object.defineProperty(this,"_errors",{value:e})}*[Symbol.iterator](){for(const e of this._errors)yield e}}e.exports=n},86873:e=>{"use strict";e.exports=({onlyFirst:e=!1}={})=>{const t=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(t,e?void 0:"g")}},78413:(e,t,i)=>{"use strict";e=i.nmd(e);const s=(e,t)=>(...i)=>`[${e(...i)+t}m`,r=(e,t)=>(...i)=>{const s=e(...i);return`[${38+t};5;${s}m`},n=(e,t)=>(...i)=>{const s=e(...i);return`[${38+t};2;${s[0]};${s[1]};${s[2]}m`},a=e=>e,o=(e,t,i)=>[e,t,i],c=(e,t,i)=>{Object.defineProperty(e,t,{get:()=>{const s=i();return Object.defineProperty(e,t,{value:s,enumerable:!0,configurable:!0}),s},enumerable:!0,configurable:!0})};let l;const p=(e,t,s,r)=>{void 0===l&&(l=i(60398));const n=r?10:0,a={};for(const[i,r]of Object.entries(l)){const o="ansi16"===i?"ansi":i;i===t?a[o]=e(s,n):"object"==typeof r&&(a[o]=e(r[t],n))}return a};Object.defineProperty(e,"exports",{enumerable:!0,get:function(){const e=new Map,t={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};t.color.gray=t.color.blackBright,t.bgColor.bgGray=t.bgColor.bgBlackBright,t.color.grey=t.color.blackBright,t.bgColor.bgGrey=t.bgColor.bgBlackBright;for(const[i,s]of Object.entries(t)){for(const[i,r]of Object.entries(s))t[i]={open:`[${r[0]}m`,close:`[${r[1]}m`},s[i]=t[i],e.set(r[0],r[1]);Object.defineProperty(t,i,{value:s,enumerable:!1})}return Object.defineProperty(t,"codes",{value:e,enumerable:!1}),t.color.close="",t.bgColor.close="",c(t.color,"ansi",(()=>p(s,"ansi16",a,!1))),c(t.color,"ansi256",(()=>p(r,"ansi256",a,!1))),c(t.color,"ansi16m",(()=>p(n,"rgb",o,!1))),c(t.bgColor,"ansi",(()=>p(s,"ansi16",a,!0))),c(t.bgColor,"ansi256",(()=>p(r,"ansi256",a,!0))),c(t.bgColor,"ansi16m",(()=>p(n,"rgb",o,!0))),t}})},62720:(e,t,i)=>{e.exports={parallel:i(61286),serial:i(74694),serialOrdered:i(87458)}},34653:e=>{function t(e){"function"==typeof this.jobs[e]&&this.jobs[e]()}e.exports=function(e){Object.keys(e.jobs).forEach(t.bind(e)),e.jobs={}}},5209:(e,t,i)=>{var s=i(45623);e.exports=function(e){var t=!1;return s((function(){t=!0})),function(i,r){t?e(i,r):s((function(){e(i,r)}))}}},45623:e=>{e.exports=function(e){var t="function"==typeof setImmediate?setImmediate:"object"==typeof process&&"function"==typeof process.nextTick?process.nextTick:null;t?t(e):setTimeout(e,0)}},28773:(e,t,i)=>{var s=i(5209),r=i(34653);e.exports=function(e,t,i,n){var a=i.keyedList?i.keyedList[i.index]:i.index;i.jobs[a]=function(e,t,i,r){return 2==e.length?e(i,s(r)):e(i,t,s(r))}(t,a,e[a],(function(e,t){a in i.jobs&&(delete i.jobs[a],e?r(i):i.results[a]=t,n(e,i.results))}))}},67630:e=>{e.exports=function(e,t){var i=!Array.isArray(e),s={index:0,keyedList:i||t?Object.keys(e):null,jobs:{},results:i?{}:[],size:i?Object.keys(e).length:e.length};return t&&s.keyedList.sort(i?t:function(i,s){return t(e[i],e[s])}),s}},45067:(e,t,i)=>{var s=i(34653),r=i(5209);e.exports=function(e){Object.keys(this.jobs).length&&(this.index=this.size,s(this),r(e)(null,this.results))}},61286:(e,t,i)=>{var s=i(28773),r=i(67630),n=i(45067);e.exports=function(e,t,i){for(var a=r(e);a.index<(a.keyedList||e).length;)s(e,t,a,(function(e,t){e?i(e,t):0!==Object.keys(a.jobs).length||i(null,a.results)})),a.index++;return n.bind(a,i)}},74694:(e,t,i)=>{var s=i(87458);e.exports=function(e,t,i){return s(e,t,null,i)}},87458:(e,t,i)=>{var s=i(28773),r=i(67630),n=i(45067);function a(e,t){return et?1:0}e.exports=function(e,t,i,a){var o=r(e,i);return s(e,t,o,(function i(r,n){r?a(r,n):(o.index++,o.index<(o.keyedList||e).length?s(e,t,o,i):a(null,o.results))})),n.bind(o,a)},e.exports.ascending=a,e.exports.descending=function(e,t){return-1*a(e,t)}},72547:e=>{"use strict";function t(e,t,r){e instanceof RegExp&&(e=i(e,r)),t instanceof RegExp&&(t=i(t,r));var n=s(e,t,r);return n&&{start:n[0],end:n[1],pre:r.slice(0,n[0]),body:r.slice(n[0]+e.length,n[1]),post:r.slice(n[1]+t.length)}}function i(e,t){var i=t.match(e);return i?i[0]:null}function s(e,t,i){var s,r,n,a,o,c=i.indexOf(e),l=i.indexOf(t,c+1),p=c;if(c>=0&&l>0){if(e===t)return[c,l];for(s=[],n=i.length;p>=0&&!o;)p==c?(s.push(p),c=i.indexOf(e,p+1)):1==s.length?o=[s.pop(),l]:((r=s.pop())=0?c:l;s.length&&(o=[n,a])}return o}e.exports=t,t.range=s},8903:(e,t,i)=>{var s=i(46459),r=i(32361),n=i(62235),a=Function.bind,o=a.bind(a);function c(e,t,i){var s=o(n,null).apply(null,i?[t,i]:[t]);e.api={remove:s},e.remove=s,["before","error","after","wrap"].forEach((function(s){var n=i?[t,s,i]:[t,s];e[s]=e.api[s]=o(r,null).apply(null,n)}))}function l(){var e={registry:{}},t=s.bind(null,e);return c(t,e),t}var p=!1;function A(){return p||(console.warn('[before-after-hook]: "Hook()" repurposing warning, use "Hook.Collection()". Read more: https://git.io/upgrade-before-after-hook-to-1.4'),p=!0),l()}A.Singular=function(){var e={registry:{}},t=s.bind(null,e,"h");return c(t,e,"h"),t}.bind(),A.Collection=l.bind(),e.exports=A,e.exports.Hook=A,e.exports.Singular=A.Singular,e.exports.Collection=A.Collection},32361:e=>{e.exports=function(e,t,i,s){var r=s;e.registry[i]||(e.registry[i]=[]),"before"===t&&(s=function(e,t){return Promise.resolve().then(r.bind(null,t)).then(e.bind(null,t))}),"after"===t&&(s=function(e,t){var i;return Promise.resolve().then(e.bind(null,t)).then((function(e){return r(i=e,t)})).then((function(){return i}))}),"error"===t&&(s=function(e,t){return Promise.resolve().then(e.bind(null,t)).catch((function(e){return r(e,t)}))}),e.registry[i].push({hook:s,orig:r})}},46459:e=>{e.exports=function e(t,i,s,r){if("function"!=typeof s)throw new Error("method for before hook must be a function");return r||(r={}),Array.isArray(i)?i.reverse().reduce((function(i,s){return e.bind(null,t,s,i,r)}),s)():Promise.resolve().then((function(){return t.registry[i]?t.registry[i].reduce((function(e,t){return t.hook.bind(null,e,r)}),s)():s(r)}))}},62235:e=>{e.exports=function(e,t,i){if(e.registry[t]){var s=e.registry[t].map((function(e){return e.orig})).indexOf(i);-1!==s&&e.registry[t].splice(s,1)}}},20276:(e,t,i)=>{"use strict";const{Buffer:s}=i(14300),r=Symbol.for("BufferList");function n(e){if(!(this instanceof n))return new n(e);n._init.call(this,e)}n._init=function(e){Object.defineProperty(this,r,{value:!0}),this._bufs=[],this.length=0,e&&this.append(e)},n.prototype._new=function(e){return new n(e)},n.prototype._offset=function(e){if(0===e)return[0,0];let t=0;for(let i=0;ithis.length||e<0)return;const t=this._offset(e);return this._bufs[t[0]][t[1]]},n.prototype.slice=function(e,t){return"number"==typeof e&&e<0&&(e+=this.length),"number"==typeof t&&t<0&&(t+=this.length),this.copy(null,0,e,t)},n.prototype.copy=function(e,t,i,r){if(("number"!=typeof i||i<0)&&(i=0),("number"!=typeof r||r>this.length)&&(r=this.length),i>=this.length)return e||s.alloc(0);if(r<=0)return e||s.alloc(0);const n=!!e,a=this._offset(i),o=r-i;let c=o,l=n&&t||0,p=a[1];if(0===i&&r===this.length){if(!n)return 1===this._bufs.length?this._bufs[0]:s.concat(this._bufs,this.length);for(let t=0;ti)){this._bufs[t].copy(e,l,p,p+c),l+=i;break}this._bufs[t].copy(e,l,p),l+=i,c-=i,p&&(p=0)}return e.length>l?e.slice(0,l):e},n.prototype.shallowSlice=function(e,t){if(e=e||0,t="number"!=typeof t?this.length:t,e<0&&(e+=this.length),t<0&&(t+=this.length),e===t)return this._new();const i=this._offset(e),s=this._offset(t),r=this._bufs.slice(i[0],s[0]+1);return 0===s[1]?r.pop():r[r.length-1]=r[r.length-1].slice(0,s[1]),0!==i[1]&&(r[0]=r[0].slice(i[1])),this._new(r)},n.prototype.toString=function(e,t,i){return this.slice(t,i).toString(e)},n.prototype.consume=function(e){if(e=Math.trunc(e),Number.isNaN(e)||e<=0)return this;for(;this._bufs.length;){if(!(e>=this._bufs[0].length)){this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift()}return this},n.prototype.duplicate=function(){const e=this._new();for(let t=0;tthis.length?this.length:t;const r=this._offset(t);let n=r[0],a=r[1];for(;n=e.length){const i=t.indexOf(e,a);if(-1!==i)return this._reverseOffset([n,i]);a=t.length-e.length+1}else{const t=this._reverseOffset([n,a]);if(this._match(t,e))return t;a++}a=0}return-1},n.prototype._match=function(e,t){if(this.length-e{"use strict";const s=i(65945).Duplex,r=i(44236),n=i(20276);function a(e){if(!(this instanceof a))return new a(e);if("function"==typeof e){this._callback=e;const t=function(e){this._callback&&(this._callback(e),this._callback=null)}.bind(this);this.on("pipe",(function(e){e.on("error",t)})),this.on("unpipe",(function(e){e.removeListener("error",t)})),e=null}n._init.call(this,e),s.call(this)}r(a,s),Object.assign(a.prototype,n.prototype),a.prototype._new=function(e){return new a(e)},a.prototype._write=function(e,t,i){this._appendBuffer(e),"function"==typeof i&&i()},a.prototype._read=function(e){if(!this.length)return this.push(null);e=Math.min(e,this.length),this.push(this.slice(0,e)),this.consume(e)},a.prototype.end=function(e){s.prototype.end.call(this,e),this._callback&&(this._callback(null,this.slice()),this._callback=null)},a.prototype._destroy=function(e,t){this._bufs.length=0,this.length=0,t(e)},a.prototype._isBufferList=function(e){return e instanceof a||e instanceof n||a.isBufferList(e)},a.isBufferList=n.isBufferList,e.exports=a,e.exports.BufferListStream=a,e.exports.BufferList=n},32722:function(e){var t;t=function(){"use strict";var e,t,i="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},s={load:function(e,t,i={}){var s,r,n;for(s in t)n=t[s],i[s]=null!=(r=e[s])?r:n;return i},overwrite:function(e,t,i={}){var s,r;for(s in e)r=e[s],void 0!==t[s]&&(i[s]=r);return i}},r=class{constructor(e,t){this.incr=e,this.decr=t,this._first=null,this._last=null,this.length=0}push(e){var t;this.length++,"function"==typeof this.incr&&this.incr(),t={value:e,prev:this._last,next:null},null!=this._last?(this._last.next=t,this._last=t):this._first=this._last=t}shift(){var e;if(null!=this._first)return this.length--,"function"==typeof this.decr&&this.decr(),e=this._first.value,null!=(this._first=this._first.next)?this._first.prev=null:this._last=null,e}first(){if(null!=this._first)return this._first.value}getArray(){var e,t,i;for(e=this._first,i=[];null!=e;)i.push((t=e,e=e.next,t.value));return i}forEachShift(e){var t;for(t=this.shift();null!=t;)e(t),t=this.shift()}debug(){var e,t,i,s,r;for(e=this._first,r=[];null!=e;)r.push((t=e,e=e.next,{value:t.value,prev:null!=(i=t.prev)?i.value:void 0,next:null!=(s=t.next)?s.value:void 0}));return r}},n=class{constructor(e){if(this.instance=e,this._events={},null!=this.instance.on||null!=this.instance.once||null!=this.instance.removeAllListeners)throw new Error("An Emitter already exists for this object");this.instance.on=(e,t)=>this._addListener(e,"many",t),this.instance.once=(e,t)=>this._addListener(e,"once",t),this.instance.removeAllListeners=(e=null)=>null!=e?delete this._events[e]:this._events={}}_addListener(e,t,i){var s;return null==(s=this._events)[e]&&(s[e]=[]),this._events[e].push({cb:i,status:t}),this.instance}listenerCount(e){return null!=this._events[e]?this._events[e].length:0}async trigger(e,...t){var i,s;try{if("debug"!==e&&this.trigger("debug",`Event triggered: ${e}`,t),null==this._events[e])return;return this._events[e]=this._events[e].filter((function(e){return"none"!==e.status})),s=this._events[e].map((async e=>{var i,s;if("none"!==e.status){"once"===e.status&&(e.status="none");try{return"function"==typeof(null!=(s="function"==typeof e.cb?e.cb(...t):void 0)?s.then:void 0)?await s:s}catch(e){return i=e,this.trigger("error",i),null}}})),(await Promise.all(s)).find((function(e){return null!=e}))}catch(e){return i=e,this.trigger("error",i),null}}};e=r,t=n;var a,o,c=class extends Error{};o=s,a=c;var l,p,A=class{constructor(e,t,i,s,r,n,a,c){this.task=e,this.args=t,this.rejectOnDrop=r,this.Events=n,this._states=a,this.Promise=c,this.options=o.load(i,s),this.options.priority=this._sanitizePriority(this.options.priority),this.options.id===s.id&&(this.options.id=`${this.options.id}-${this._randomIndex()}`),this.promise=new this.Promise(((e,t)=>{this._resolve=e,this._reject=t})),this.retryCount=0}_sanitizePriority(e){var t;return(t=~~e!==e?5:e)<0?0:t>9?9:t}_randomIndex(){return Math.random().toString(36).slice(2)}doDrop({error:e,message:t="This job has been dropped by Bottleneck"}={}){return!!this._states.remove(this.options.id)&&(this.rejectOnDrop&&this._reject(null!=e?e:new a(t)),this.Events.trigger("dropped",{args:this.args,options:this.options,task:this.task,promise:this.promise}),!0)}_assertStatus(e){var t;if((t=this._states.jobStatus(this.options.id))!==e&&("DONE"!==e||null!==t))throw new a(`Invalid job status ${t}, expected ${e}. Please open an issue at https://github.com/SGrondin/bottleneck/issues`)}doReceive(){return this._states.start(this.options.id),this.Events.trigger("received",{args:this.args,options:this.options})}doQueue(e,t){return this._assertStatus("RECEIVED"),this._states.next(this.options.id),this.Events.trigger("queued",{args:this.args,options:this.options,reachedHWM:e,blocked:t})}doRun(){return 0===this.retryCount?(this._assertStatus("QUEUED"),this._states.next(this.options.id)):this._assertStatus("EXECUTING"),this.Events.trigger("scheduled",{args:this.args,options:this.options})}async doExecute(e,t,i,s){var r,n,a;0===this.retryCount?(this._assertStatus("RUNNING"),this._states.next(this.options.id)):this._assertStatus("EXECUTING"),n={args:this.args,options:this.options,retryCount:this.retryCount},this.Events.trigger("executing",n);try{if(a=await(null!=e?e.schedule(this.options,this.task,...this.args):this.task(...this.args)),t())return this.doDone(n),await s(this.options,n),this._assertStatus("DONE"),this._resolve(a)}catch(e){return r=e,this._onFailure(r,n,t,i,s)}}doExpire(e,t,i){var s,r;return this._states.jobStatus("RUNNING"===this.options.id)&&this._states.next(this.options.id),this._assertStatus("EXECUTING"),r={args:this.args,options:this.options,retryCount:this.retryCount},s=new a(`This job timed out after ${this.options.expiration} ms.`),this._onFailure(s,r,e,t,i)}async _onFailure(e,t,i,s,r){var n,a;if(i())return null!=(n=await this.Events.trigger("failed",e,t))?(a=~~n,this.Events.trigger("retry",`Retrying ${this.options.id} after ${a} ms`,t),this.retryCount++,s(a)):(this.doDone(t),await r(this.options,t),this._assertStatus("DONE"),this._reject(e))}doDone(e){return this._assertStatus("EXECUTING"),this._states.next(this.options.id),this.Events.trigger("done",e)}};p=s,l=c;var u;u=c;var d;d=r;var h,m,g,f,E,C="2.19.5",y={version:C},v=Object.freeze({version:C,default:y}),w=()=>console.log("You must import the full version of Bottleneck in order to use this feature."),I=()=>console.log("You must import the full version of Bottleneck in order to use this feature.");E=s,h=n,g=w,m=I,f=()=>console.log("You must import the full version of Bottleneck in order to use this feature.");var B,b,Q=function(){class e{constructor(e={}){this.deleteKey=this.deleteKey.bind(this),this.limiterOptions=e,E.load(this.limiterOptions,this.defaults,this),this.Events=new h(this),this.instances={},this.Bottleneck=U,this._startAutoCleanup(),this.sharedConnection=null!=this.connection,null==this.connection&&("redis"===this.limiterOptions.datastore?this.connection=new g(Object.assign({},this.limiterOptions,{Events:this.Events})):"ioredis"===this.limiterOptions.datastore&&(this.connection=new m(Object.assign({},this.limiterOptions,{Events:this.Events}))))}key(e=""){var t;return null!=(t=this.instances[e])?t:(()=>{var t;return t=this.instances[e]=new this.Bottleneck(Object.assign(this.limiterOptions,{id:`${this.id}-${e}`,timeout:this.timeout,connection:this.connection})),this.Events.trigger("created",t,e),t})()}async deleteKey(e=""){var t,i;return i=this.instances[e],this.connection&&(t=await this.connection.__runCommand__(["del",...f.allKeys(`${this.id}-${e}`)])),null!=i&&(delete this.instances[e],await i.disconnect()),null!=i||t>0}limiters(){var e,t,i,s;for(e in i=[],t=this.instances)s=t[e],i.push({key:e,limiter:s});return i}keys(){return Object.keys(this.instances)}async clusterKeys(){var e,t,i,s,r,n,a,o;if(null==this.connection)return this.Promise.resolve(this.keys());for(r=[],e=null,o=`b_${this.id}-`.length;0!==e;)for([a,t]=await this.connection.__runCommand__(["scan",null!=e?e:0,"match",`b_${this.id}-*_settings`,"count",1e4]),e=~~a,i=0,n=t.length;i{var e,t,i,s,r,n;for(t in r=Date.now(),s=[],i=this.instances){n=i[t];try{await n._store.__groupCheck__(r)?s.push(this.deleteKey(t)):s.push(void 0)}catch(t){e=t,s.push(n.Events.trigger("error",e))}}return s}),this.timeout/2)).unref?e.unref():void 0}updateSettings(e={}){if(E.overwrite(e,this.defaults,this),E.overwrite(e,e,this.limiterOptions),null!=e.timeout)return this._startAutoCleanup()}disconnect(e=!0){var t;if(!this.sharedConnection)return null!=(t=this.connection)?t.disconnect(e):void 0}}return e.prototype.defaults={timeout:3e5,connection:null,Promise,id:"group-key"},e}.call(i);b=s,B=n;var x,k,D,S,_,R,T,F,N,L=function(){class e{constructor(e={}){this.options=e,b.load(this.options,this.defaults,this),this.Events=new B(this),this._arr=[],this._resetPromise(),this._lastFlush=Date.now()}_resetPromise(){return this._promise=new this.Promise(((e,t)=>this._resolve=e))}_flush(){return clearTimeout(this._timeout),this._lastFlush=Date.now(),this._resolve(),this.Events.trigger("batch",this._arr),this._arr=[],this._resetPromise()}add(e){var t;return this._arr.push(e),t=this._promise,this._arr.length===this.maxSize?this._flush():null!=this.maxTime&&1===this._arr.length&&(this._timeout=setTimeout((()=>this._flush()),this.maxTime)),t}}return e.prototype.defaults={maxTime:null,maxSize:null,Promise},e}.call(i),O=(x=v)&&x.default||x,M=[].splice;N=s,_=class{constructor(i){this.Events=new t(this),this._length=0,this._lists=function(){var t,s,r;for(r=[],t=1,s=i;1<=s?t<=s:t>=s;1<=s?++t:--t)r.push(new e((()=>this.incr()),(()=>this.decr())));return r}.call(this)}incr(){if(0==this._length++)return this.Events.trigger("leftzero")}decr(){if(0==--this._length)return this.Events.trigger("zero")}push(e){return this._lists[e.options.priority].push(e)}queued(e){return null!=e?this._lists[e].length:this._length}shiftAll(e){return this._lists.forEach((function(t){return t.forEachShift(e)}))}getFirst(e=this._lists){var t,i,s;for(t=0,i=e.length;t0)return s;return[]}shiftLastFrom(e){return this.getFirst(this._lists.slice(e).reverse()).shift()}},D=A,S=class{constructor(e,t,i){this.instance=e,this.storeOptions=t,this.clientId=this.instance._randomIndex(),p.load(i,i,this),this._nextRequest=this._lastReservoirRefresh=this._lastReservoirIncrease=Date.now(),this._running=0,this._done=0,this._unblockTime=0,this.ready=this.Promise.resolve(),this.clients={},this._startHeartbeat()}_startHeartbeat(){var e;return null==this.heartbeat&&(null!=this.storeOptions.reservoirRefreshInterval&&null!=this.storeOptions.reservoirRefreshAmount||null!=this.storeOptions.reservoirIncreaseInterval&&null!=this.storeOptions.reservoirIncreaseAmount)?"function"==typeof(e=this.heartbeat=setInterval((()=>{var e,t,i,s,r;if(s=Date.now(),null!=this.storeOptions.reservoirRefreshInterval&&s>=this._lastReservoirRefresh+this.storeOptions.reservoirRefreshInterval&&(this._lastReservoirRefresh=s,this.storeOptions.reservoir=this.storeOptions.reservoirRefreshAmount,this.instance._drainAll(this.computeCapacity())),null!=this.storeOptions.reservoirIncreaseInterval&&s>=this._lastReservoirIncrease+this.storeOptions.reservoirIncreaseInterval&&(({reservoirIncreaseAmount:e,reservoirIncreaseMaximum:i,reservoir:r}=this.storeOptions),this._lastReservoirIncrease=s,(t=null!=i?Math.min(e,i-r):e)>0))return this.storeOptions.reservoir+=t,this.instance._drainAll(this.computeCapacity())}),this.heartbeatInterval)).unref?e.unref():void 0:clearInterval(this.heartbeat)}async __publish__(e){return await this.yieldLoop(),this.instance.Events.trigger("message",e.toString())}async __disconnect__(e){return await this.yieldLoop(),clearInterval(this.heartbeat),this.Promise.resolve()}yieldLoop(e=0){return new this.Promise((function(t,i){return setTimeout(t,e)}))}computePenalty(){var e;return null!=(e=this.storeOptions.penalty)?e:15*this.storeOptions.minTime||5e3}async __updateSettings__(e){return await this.yieldLoop(),p.overwrite(e,e,this.storeOptions),this._startHeartbeat(),this.instance._drainAll(this.computeCapacity()),!0}async __running__(){return await this.yieldLoop(),this._running}async __queued__(){return await this.yieldLoop(),this.instance.queued()}async __done__(){return await this.yieldLoop(),this._done}async __groupCheck__(e){return await this.yieldLoop(),this._nextRequest+this.timeout=e}check(e,t){return this.conditionsCheck(e)&&this._nextRequest-t<=0}async __check__(e){var t;return await this.yieldLoop(),t=Date.now(),this.check(e,t)}async __register__(e,t,i){var s,r;return await this.yieldLoop(),s=Date.now(),this.conditionsCheck(t)?(this._running+=t,null!=this.storeOptions.reservoir&&(this.storeOptions.reservoir-=t),r=Math.max(this._nextRequest-s,0),this._nextRequest=s+r+this.storeOptions.minTime,{success:!0,wait:r,reservoir:this.storeOptions.reservoir}):{success:!1}}strategyIsBlock(){return 3===this.storeOptions.strategy}async __submit__(e,t){var i,s,r;if(await this.yieldLoop(),null!=this.storeOptions.maxConcurrent&&t>this.storeOptions.maxConcurrent)throw new l(`Impossible to add a job having a weight of ${t} to a limiter having a maxConcurrent setting of ${this.storeOptions.maxConcurrent}`);return s=Date.now(),r=null!=this.storeOptions.highWater&&e===this.storeOptions.highWater&&!this.check(t,s),(i=this.strategyIsBlock()&&(r||this.isBlocked(s)))&&(this._unblockTime=s+this.computePenalty(),this._nextRequest=this._unblockTime+this.storeOptions.minTime,this.instance._dropAllQueued()),{reachedHWM:r,blocked:i,strategy:this.storeOptions.strategy}}async __free__(e,t){return await this.yieldLoop(),this._running-=t,this._done+=t,this.instance._drainAll(this.computeCapacity()),{running:this._running}}},R=()=>console.log("You must import the full version of Bottleneck in order to use this feature."),k=n,T=class{constructor(e){this.status=e,this._jobs={},this.counts=this.status.map((function(){return 0}))}next(e){var t,i;return i=(t=this._jobs[e])+1,null!=t&&i(e[this.status[i]]=t,e)),{})}},F=class{constructor(e,t){this.schedule=this.schedule.bind(this),this.name=e,this.Promise=t,this._running=0,this._queue=new d}isEmpty(){return 0===this._queue.length}async _tryToRun(){var e,t,i,s,r,n,a;if(this._running<1&&this._queue.length>0)return this._running++,({task:a,args:e,resolve:r,reject:s}=this._queue.shift()),t=await async function(){try{return n=await a(...e),function(){return r(n)}}catch(e){return i=e,function(){return s(i)}}}(),this._running--,this._tryToRun(),t()}schedule(e,...t){var i,s,r;return r=s=null,i=new this.Promise((function(e,t){return r=e,s=t})),this._queue.push({task:e,args:t,resolve:r,reject:s}),this._tryToRun(),i}};var U=function(){class e{constructor(t={},...i){var s,r;this._addToQueue=this._addToQueue.bind(this),this._validateOptions(t,i),N.load(t,this.instanceDefaults,this),this._queues=new _(10),this._scheduled={},this._states=new T(["RECEIVED","QUEUED","RUNNING","EXECUTING"].concat(this.trackDoneStatus?["DONE"]:[])),this._limiter=null,this.Events=new k(this),this._submitLock=new F("submit",this.Promise),this._registerLock=new F("register",this.Promise),r=N.load(t,this.storeDefaults,{}),this._store=function(){if("redis"===this.datastore||"ioredis"===this.datastore||null!=this.connection)return s=N.load(t,this.redisStoreDefaults,{}),new R(this,r,s);if("local"===this.datastore)return s=N.load(t,this.localStoreDefaults,{}),new S(this,r,s);throw new e.prototype.BottleneckError(`Invalid datastore type: ${this.datastore}`)}.call(this),this._queues.on("leftzero",(()=>{var e;return null!=(e=this._store.heartbeat)&&"function"==typeof e.ref?e.ref():void 0})),this._queues.on("zero",(()=>{var e;return null!=(e=this._store.heartbeat)&&"function"==typeof e.unref?e.unref():void 0}))}_validateOptions(t,i){if(null==t||"object"!=typeof t||0!==i.length)throw new e.prototype.BottleneckError("Bottleneck v2 takes a single object argument. Refer to https://github.com/SGrondin/bottleneck#upgrading-to-v2 if you're upgrading from Bottleneck v1.")}ready(){return this._store.ready}clients(){return this._store.clients}channel(){return`b_${this.id}`}channel_client(){return`b_${this.id}_${this._store.clientId}`}publish(e){return this._store.__publish__(e)}disconnect(e=!0){return this._store.__disconnect__(e)}chain(e){return this._limiter=e,this}queued(e){return this._queues.queued(e)}clusterQueued(){return this._store.__queued__()}empty(){return 0===this.queued()&&this._submitLock.isEmpty()}running(){return this._store.__running__()}done(){return this._store.__done__()}jobStatus(e){return this._states.jobStatus(e)}jobs(e){return this._states.statusJobs(e)}counts(){return this._states.statusCounts()}_randomIndex(){return Math.random().toString(36).slice(2)}check(e=1){return this._store.__check__(e)}_clearGlobalState(e){return null!=this._scheduled[e]&&(clearTimeout(this._scheduled[e].expiration),delete this._scheduled[e],!0)}async _free(e,t,i,s){var r,n;try{if(({running:n}=await this._store.__free__(e,i.weight)),this.Events.trigger("debug",`Freed ${i.id}`,s),0===n&&this.empty())return this.Events.trigger("idle")}catch(e){return r=e,this.Events.trigger("error",r)}}_run(e,t,i){var s,r,n;return t.doRun(),s=this._clearGlobalState.bind(this,e),n=this._run.bind(this,e,t),r=this._free.bind(this,e,t),this._scheduled[e]={timeout:setTimeout((()=>t.doExecute(this._limiter,s,n,r)),i),expiration:null!=t.options.expiration?setTimeout((function(){return t.doExpire(s,n,r)}),i+t.options.expiration):void 0,job:t}}_drainOne(e){return this._registerLock.schedule((()=>{var t,i,s,r,n;return 0===this.queued()?this.Promise.resolve(null):(n=this._queues.getFirst(),({options:r,args:t}=s=n.first()),null!=e&&r.weight>e?this.Promise.resolve(null):(this.Events.trigger("debug",`Draining ${r.id}`,{args:t,options:r}),i=this._randomIndex(),this._store.__register__(i,r.weight,r.expiration).then((({success:e,wait:a,reservoir:o})=>{var c;return this.Events.trigger("debug",`Drained ${r.id}`,{success:e,args:t,options:r}),e?(n.shift(),(c=this.empty())&&this.Events.trigger("empty"),0===o&&this.Events.trigger("depleted",c),this._run(i,s,a),this.Promise.resolve(r.weight)):this.Promise.resolve(null)}))))}))}_drainAll(e,t=0){return this._drainOne(e).then((i=>{var s;return null!=i?(s=null!=e?e-i:e,this._drainAll(s,t+i)):this.Promise.resolve(t)})).catch((e=>this.Events.trigger("error",e)))}_dropAllQueued(e){return this._queues.shiftAll((function(t){return t.doDrop({message:e})}))}stop(t={}){var i,s;return t=N.load(t,this.stopDefaults),s=e=>{var t;return t=()=>{var t;return(t=this._states.counts)[0]+t[1]+t[2]+t[3]===e},new this.Promise(((e,i)=>t()?e():this.on("done",(()=>{if(t())return this.removeAllListeners("done"),e()}))))},i=t.dropWaitingJobs?(this._run=function(e,i){return i.doDrop({message:t.dropErrorMessage})},this._drainOne=()=>this.Promise.resolve(null),this._registerLock.schedule((()=>this._submitLock.schedule((()=>{var e,i,r;for(e in i=this._scheduled)r=i[e],"RUNNING"===this.jobStatus(r.job.options.id)&&(clearTimeout(r.timeout),clearTimeout(r.expiration),r.job.doDrop({message:t.dropErrorMessage}));return this._dropAllQueued(t.dropErrorMessage),s(0)}))))):this.schedule({priority:9,weight:0},(()=>s(1))),this._receive=function(i){return i._reject(new e.prototype.BottleneckError(t.enqueueErrorMessage))},this.stop=()=>this.Promise.reject(new e.prototype.BottleneckError("stop() has already been called")),i}async _addToQueue(t){var i,s,r,n,a,o,c;({args:i,options:n}=t);try{({reachedHWM:a,blocked:s,strategy:c}=await this._store.__submit__(this.queued(),n.weight))}catch(e){return r=e,this.Events.trigger("debug",`Could not queue ${n.id}`,{args:i,options:n,error:r}),t.doDrop({error:r}),!1}return s?(t.doDrop(),!0):a&&(null!=(o=c===e.prototype.strategy.LEAK?this._queues.shiftLastFrom(n.priority):c===e.prototype.strategy.OVERFLOW_PRIORITY?this._queues.shiftLastFrom(n.priority+1):c===e.prototype.strategy.OVERFLOW?t:void 0)&&o.doDrop(),null==o||c===e.prototype.strategy.OVERFLOW)?(null==o&&t.doDrop(),a):(t.doQueue(a,s),this._queues.push(t),await this._drainAll(),a)}_receive(t){return null!=this._states.jobStatus(t.options.id)?(t._reject(new e.prototype.BottleneckError(`A job with the same id already exists (id=${t.options.id})`)),!1):(t.doReceive(),this._submitLock.schedule(this._addToQueue,t))}submit(...e){var t,i,s,r,n,a,o;return"function"==typeof e[0]?(n=e,[i,...e]=n,[t]=M.call(e,-1),r=N.load({},this.jobDefaults)):(a=e,[r,i,...e]=a,[t]=M.call(e,-1),r=N.load(r,this.jobDefaults)),o=(...e)=>new this.Promise((function(t,s){return i(...e,(function(...e){return(null!=e[0]?s:t)(e)}))})),(s=new D(o,e,r,this.jobDefaults,this.rejectOnDrop,this.Events,this._states,this.Promise)).promise.then((function(e){return"function"==typeof t?t(...e):void 0})).catch((function(e){return Array.isArray(e)?"function"==typeof t?t(...e):void 0:"function"==typeof t?t(e):void 0})),this._receive(s)}schedule(...e){var t,i,s;return"function"==typeof e[0]?([s,...e]=e,i={}):[i,s,...e]=e,t=new D(s,e,i,this.jobDefaults,this.rejectOnDrop,this.Events,this._states,this.Promise),this._receive(t),t.promise}wrap(e){var t,i;return t=this.schedule.bind(this),(i=function(...i){return t(e.bind(this),...i)}).withOptions=function(i,...s){return t(i,e,...s)},i}async updateSettings(e={}){return await this._store.__updateSettings__(N.overwrite(e,this.storeDefaults)),N.overwrite(e,this.instanceDefaults,this),this}currentReservoir(){return this._store.__currentReservoir__()}incrementReservoir(e=0){return this._store.__incrementReservoir__(e)}}return e.default=e,e.Events=k,e.version=e.prototype.version=O.version,e.strategy=e.prototype.strategy={LEAK:1,OVERFLOW:2,OVERFLOW_PRIORITY:4,BLOCK:3},e.BottleneckError=e.prototype.BottleneckError=c,e.Group=e.prototype.Group=Q,e.RedisConnection=e.prototype.RedisConnection=w,e.IORedisConnection=e.prototype.IORedisConnection=I,e.Batcher=e.prototype.Batcher=L,e.prototype.jobDefaults={priority:5,weight:1,expiration:null,id:""},e.prototype.storeDefaults={maxConcurrent:null,minTime:0,highWater:null,strategy:e.prototype.strategy.LEAK,penalty:null,reservoir:null,reservoirRefreshInterval:null,reservoirRefreshAmount:null,reservoirIncreaseInterval:null,reservoirIncreaseAmount:null,reservoirIncreaseMaximum:null},e.prototype.localStoreDefaults={Promise,timeout:null,heartbeatInterval:250},e.prototype.redisStoreDefaults={Promise,timeout:null,heartbeatInterval:5e3,clientTimeout:1e4,Redis:null,clientOptions:{},clusterNodes:null,clearDatastore:!1,connection:null},e.prototype.instanceDefaults={datastore:"local",connection:null,id:"",rejectOnDrop:!0,trackDoneStatus:!1,Promise},e.prototype.stopDefaults={enqueueErrorMessage:"This limiter has been stopped and cannot accept new jobs.",dropWaitingJobs:!0,dropErrorMessage:"This limiter has been stopped."},e}.call(i);return U},e.exports=t()},58389:(e,t,i)=>{var s=i(72547);e.exports=function(e){return e?("{}"===e.substr(0,2)&&(e="\\{\\}"+e.substr(2)),g(function(e){return e.split("\\\\").join(r).split("\\{").join(n).split("\\}").join(a).split("\\,").join(o).split("\\.").join(c)}(e),!0).map(p)):[]};var r="\0SLASH"+Math.random()+"\0",n="\0OPEN"+Math.random()+"\0",a="\0CLOSE"+Math.random()+"\0",o="\0COMMA"+Math.random()+"\0",c="\0PERIOD"+Math.random()+"\0";function l(e){return parseInt(e,10)==e?parseInt(e,10):e.charCodeAt(0)}function p(e){return e.split(r).join("\\").split(n).join("{").split(a).join("}").split(o).join(",").split(c).join(".")}function A(e){if(!e)return[""];var t=[],i=s("{","}",e);if(!i)return e.split(",");var r=i.pre,n=i.body,a=i.post,o=r.split(",");o[o.length-1]+="{"+n+"}";var c=A(a);return a.length&&(o[o.length-1]+=c.shift(),o.push.apply(o,c)),t.push.apply(t,o),t}function u(e){return"{"+e+"}"}function d(e){return/^-?0\d/.test(e)}function h(e,t){return e<=t}function m(e,t){return e>=t}function g(e,t){var i=[],r=s("{","}",e);if(!r)return[e];var n=r.pre,o=r.post.length?g(r.post,!1):[""];if(/\$$/.test(r.pre))for(var c=0;c=0;if(!v&&!w)return r.post.match(/,.*\}/)?g(e=r.pre+"{"+r.body+a+r.post):[e];if(v)f=r.body.split(/\.\./);else if(1===(f=A(r.body)).length&&1===(f=g(f[0],!1).map(u)).length)return o.map((function(e){return r.pre+f[0]+e}));if(v){var I=l(f[0]),B=l(f[1]),b=Math.max(f[0].length,f[1].length),Q=3==f.length?Math.abs(l(f[2])):1,x=h;B0){var R=new Array(_+1).join("0");S=D<0?"-"+R+S.slice(1):R+S}}E.push(S)}}else{E=[];for(var T=0;T{e.exports=function(e){return new Buffer(e).toString("base64")}},47309:(e,t,i)=>{"use strict";const s=i(78413),{stdout:r,stderr:n}=i(10447),{stringReplaceAll:a,stringEncaseCRLFWithFirstIndex:o}=i(63370),{isArray:c}=Array,l=["ansi","ansi","ansi256","ansi16m"],p=Object.create(null);class A{constructor(e){return u(e)}}const u=e=>{const t={};return((e,t={})=>{if(t.level&&!(Number.isInteger(t.level)&&t.level>=0&&t.level<=3))throw new Error("The `level` option should be an integer from 0 to 3");const i=r?r.level:0;e.level=void 0===t.level?i:t.level})(t,e),t.template=(...e)=>y(t.template,...e),Object.setPrototypeOf(t,d.prototype),Object.setPrototypeOf(t.template,t),t.template.constructor=()=>{throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.")},t.template.Instance=A,t.template};function d(e){return u(e)}for(const[e,t]of Object.entries(s))p[e]={get(){const i=f(this,g(t.open,t.close,this._styler),this._isEmpty);return Object.defineProperty(this,e,{value:i}),i}};p.visible={get(){const e=f(this,this._styler,!0);return Object.defineProperty(this,"visible",{value:e}),e}};const h=["rgb","hex","keyword","hsl","hsv","hwb","ansi","ansi256"];for(const e of h)p[e]={get(){const{level:t}=this;return function(...i){const r=g(s.color[l[t]][e](...i),s.color.close,this._styler);return f(this,r,this._isEmpty)}}};for(const e of h)p["bg"+e[0].toUpperCase()+e.slice(1)]={get(){const{level:t}=this;return function(...i){const r=g(s.bgColor[l[t]][e](...i),s.bgColor.close,this._styler);return f(this,r,this._isEmpty)}}};const m=Object.defineProperties((()=>{}),{...p,level:{enumerable:!0,get(){return this._generator.level},set(e){this._generator.level=e}}}),g=(e,t,i)=>{let s,r;return void 0===i?(s=e,r=t):(s=i.openAll+e,r=t+i.closeAll),{open:e,close:t,openAll:s,closeAll:r,parent:i}},f=(e,t,i)=>{const s=(...e)=>c(e[0])&&c(e[0].raw)?E(s,y(s,...e)):E(s,1===e.length?""+e[0]:e.join(" "));return Object.setPrototypeOf(s,m),s._generator=e,s._styler=t,s._isEmpty=i,s},E=(e,t)=>{if(e.level<=0||!t)return e._isEmpty?"":t;let i=e._styler;if(void 0===i)return t;const{openAll:s,closeAll:r}=i;if(-1!==t.indexOf(""))for(;void 0!==i;)t=a(t,i.close,i.open),i=i.parent;const n=t.indexOf("\n");return-1!==n&&(t=o(t,r,s,n)),s+t+r};let C;const y=(e,...t)=>{const[s]=t;if(!c(s)||!c(s.raw))return t.join(" ");const r=t.slice(1),n=[s.raw[0]];for(let e=1;e{"use strict";const t=/(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi,i=/(?:^|\.)(\w+)(?:\(([^)]*)\))?/g,s=/^(['"])((?:\\.|(?!\1)[^\\])*)\1$/,r=/\\(u(?:[a-f\d]{4}|{[a-f\d]{1,6}})|x[a-f\d]{2}|.)|([^\\])/gi,n=new Map([["n","\n"],["r","\r"],["t","\t"],["b","\b"],["f","\f"],["v","\v"],["0","\0"],["\\","\\"],["e",""],["a",""]]);function a(e){const t="u"===e[0],i="{"===e[1];return t&&!i&&5===e.length||"x"===e[0]&&3===e.length?String.fromCharCode(parseInt(e.slice(1),16)):t&&i?String.fromCodePoint(parseInt(e.slice(2,-1),16)):n.get(e)||e}function o(e,t){const i=[],n=t.trim().split(/\s*,\s*/g);let o;for(const t of n){const n=Number(t);if(Number.isNaN(n)){if(!(o=t.match(s)))throw new Error(`Invalid Chalk template style argument: ${t} (in style '${e}')`);i.push(o[2].replace(r,((e,t,i)=>t?a(t):i)))}else i.push(n)}return i}function c(e){i.lastIndex=0;const t=[];let s;for(;null!==(s=i.exec(e));){const e=s[1];if(s[2]){const i=o(e,s[2]);t.push([e].concat(i))}else t.push([e])}return t}function l(e,t){const i={};for(const e of t)for(const t of e.styles)i[t[0]]=e.inverse?null:t.slice(1);let s=e;for(const[e,t]of Object.entries(i))if(Array.isArray(t)){if(!(e in s))throw new Error(`Unknown Chalk style: ${e}`);s=t.length>0?s[e](...t):s[e]}return s}e.exports=(e,i)=>{const s=[],r=[];let n=[];if(i.replace(t,((t,i,o,p,A,u)=>{if(i)n.push(a(i));else if(p){const t=n.join("");n=[],r.push(0===s.length?t:l(e,s)(t)),s.push({inverse:o,styles:c(p)})}else if(A){if(0===s.length)throw new Error("Found extraneous } in Chalk template literal");r.push(l(e,s)(n.join(""))),n=[],s.pop()}else n.push(u)})),r.push(n.join("")),s.length>0){const e=`Chalk template literal is missing ${s.length} closing bracket${1===s.length?"":"s"} (\`}\`)`;throw new Error(e)}return r.join("")}},63370:e=>{"use strict";e.exports={stringReplaceAll:(e,t,i)=>{let s=e.indexOf(t);if(-1===s)return e;const r=t.length;let n=0,a="";do{a+=e.substr(n,s-n)+t+i,n=s+r,s=e.indexOf(t,n)}while(-1!==s);return a+=e.substr(n),a},stringEncaseCRLFWithFirstIndex:(e,t,i,s)=>{let r=0,n="";do{const a="\r"===e[s-1];n+=e.substr(r,(a?s-1:s)-r)+t+(a?"\r\n":"\n")+i,r=s+1,s=e.indexOf("\n",r)}while(-1!==s);return n+=e.substr(r),n}}},53072:(e,t,i)=>{"use strict";const s=i(22037),r=/\s+at.*(?:\(|\s)(.*)\)?/,n=/^(?:(?:(?:node|(?:internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)\.js:\d+:\d+)|native)/,a=void 0===s.homedir?"":s.homedir();e.exports=(e,t)=>(t=Object.assign({pretty:!1},t),e.replace(/\\/g,"/").split("\n").filter((e=>{const t=e.match(r);if(null===t||!t[1])return!0;const i=t[1];return!i.includes(".app/Contents/Resources/electron.asar")&&!i.includes(".app/Contents/Resources/default_app.asar")&&!n.test(i)})).filter((e=>""!==e.trim())).map((e=>t.pretty?e.replace(r,((e,t)=>e.replace(t,t.replace(a,"~")))):e)).join("\n"))},55693:(e,t,i)=>{"use strict";const s=i(60350);let r=!1;t.show=(e=process.stderr)=>{e.isTTY&&(r=!1,e.write("[?25h"))},t.hide=(e=process.stderr)=>{e.isTTY&&(s(),r=!0,e.write("[?25l"))},t.toggle=(e,i)=>{void 0!==e&&(r=e),r?t.show(i):t.hide(i)}},60881:(e,t,i)=>{"use strict";const s=Object.assign({},i(36432)),r=Object.keys(s);Object.defineProperty(s,"random",{get(){const e=Math.floor(Math.random()*r.length),t=r[e];return s[t]}}),e.exports=s},91969:(e,t,i)=>{var s=i(27072),r=i(34045),n=r.repeat,a=r.truncate,o=r.pad;function c(e){if(this.options=r.options({chars:{top:"─","top-mid":"┬","top-left":"┌","top-right":"┐",bottom:"─","bottom-mid":"┴","bottom-left":"└","bottom-right":"┘",left:"│","left-mid":"├",mid:"─","mid-mid":"┼",right:"│","right-mid":"┤",middle:"│"},truncate:"…",colWidths:[],colAligns:[],style:{"padding-left":1,"padding-right":1,head:["red"],border:["grey"],compact:!1},head:[]},e),e&&e.rows)for(var t=0;tr&&(r=n),s.push({contents:i,height:n})}));var c=new Array(r);s.forEach((function(e,s){e.contents.forEach((function(e,r){c[r]||(c[r]=[]),(i||o&&0===s&&t.style.head)&&(e=C(t.style.head,e)),c[r].push(e)}));for(var n=e.height,a=r;n0&&(p+="\n"+C(t.style.border,l.left)),p+=e.join(C(t.style.border,l.middle))+C(t.style.border,l.right)})),C(t.style.border,l.left)+p}function C(e,t){return t?(e.forEach((function(e){t=s[e](t)})),t):""}function y(e,s){e=String("object"==typeof e&&e.text?e.text:e);var c=r.strlen(e),l=A[s]-(i["padding-left"]||0)-(i["padding-right"]||0),u=t.colAligns[s]||"left";return n(" ",i["padding-left"]||0)+(c==l?e:c{t.repeat=function(e,t){return Array(t+1).join(e)},t.pad=function(e,t,i,s){if(t+1>=e.length)switch(s){case"left":e=Array(t+1-e.length).join(i)+e;break;case"both":var r=Math.ceil((padlen=t-e.length)/2),n=padlen-r;e=Array(n+1).join(i)+e+Array(r+1).join(i);break;default:e+=Array(t+1-e.length).join(i)}return e},t.truncate=function(e,t,i){return i=i||"…",e.length>=t?e.substr(0,t-i.length)+i:e},t.options=function e(t,i){for(var s in i)"__proto__"!==s&&"constructor"!==s&&"prototype"!==s&&(i[s]&&i[s].constructor&&i[s].constructor===Object?(t[s]=t[s]||{},e(t[s],i[s])):t[s]=i[s]);return t},t.strlen=function(e){return(""+e).replace(/\u001b\[(?:\d*;){0,5}\d*m/g,"").split("\n").reduce((function(e,t){return t.length>e?t.length:e}),0)}},54256:e=>{var t=function(){"use strict";function e(t,s,r,n){"object"==typeof s&&(r=s.depth,n=s.prototype,s.filter,s=s.circular);var a=[],o=[],c="undefined"!=typeof Buffer;return void 0===s&&(s=!0),void 0===r&&(r=1/0),function t(r,l){if(null===r)return null;if(0==l)return r;var p,A;if("object"!=typeof r)return r;if(e.__isArray(r))p=[];else if(e.__isRegExp(r))p=new RegExp(r.source,i(r)),r.lastIndex&&(p.lastIndex=r.lastIndex);else if(e.__isDate(r))p=new Date(r.getTime());else{if(c&&Buffer.isBuffer(r))return p=Buffer.allocUnsafe?Buffer.allocUnsafe(r.length):new Buffer(r.length),r.copy(p),p;void 0===n?(A=Object.getPrototypeOf(r),p=Object.create(A)):(p=Object.create(n),A=n)}if(s){var u=a.indexOf(r);if(-1!=u)return o[u];a.push(r),o.push(p)}for(var d in r){var h;A&&(h=Object.getOwnPropertyDescriptor(A,d)),h&&null==h.set||(p[d]=t(r[d],l-1))}return p}(t,r)}function t(e){return Object.prototype.toString.call(e)}function i(e){var t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),t}return e.clonePrototype=function(e){if(null===e)return null;var t=function(){};return t.prototype=e,new t},e.__objToStr=t,e.__isDate=function(e){return"object"==typeof e&&"[object Date]"===t(e)},e.__isArray=function(e){return"object"==typeof e&&"[object Array]"===t(e)},e.__isRegExp=function(e){return"object"==typeof e&&"[object RegExp]"===t(e)},e.__getRegExpFlags=i,e}();e.exports&&(e.exports=t)},38097:(e,t,i)=>{const s=i(61821),r={};for(const e of Object.keys(s))r[s[e]]=e;const n={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};e.exports=n;for(const e of Object.keys(n)){if(!("channels"in n[e]))throw new Error("missing channels property: "+e);if(!("labels"in n[e]))throw new Error("missing channel labels property: "+e);if(n[e].labels.length!==n[e].channels)throw new Error("channel and label counts mismatch: "+e);const{channels:t,labels:i}=n[e];delete n[e].channels,delete n[e].labels,Object.defineProperty(n[e],"channels",{value:t}),Object.defineProperty(n[e],"labels",{value:i})}n.rgb.hsl=function(e){const t=e[0]/255,i=e[1]/255,s=e[2]/255,r=Math.min(t,i,s),n=Math.max(t,i,s),a=n-r;let o,c;n===r?o=0:t===n?o=(i-s)/a:i===n?o=2+(s-t)/a:s===n&&(o=4+(t-i)/a),o=Math.min(60*o,360),o<0&&(o+=360);const l=(r+n)/2;return c=n===r?0:l<=.5?a/(n+r):a/(2-n-r),[o,100*c,100*l]},n.rgb.hsv=function(e){let t,i,s,r,n;const a=e[0]/255,o=e[1]/255,c=e[2]/255,l=Math.max(a,o,c),p=l-Math.min(a,o,c),A=function(e){return(l-e)/6/p+.5};return 0===p?(r=0,n=0):(n=p/l,t=A(a),i=A(o),s=A(c),a===l?r=s-i:o===l?r=1/3+t-s:c===l&&(r=2/3+i-t),r<0?r+=1:r>1&&(r-=1)),[360*r,100*n,100*l]},n.rgb.hwb=function(e){const t=e[0],i=e[1];let s=e[2];const r=n.rgb.hsl(e)[0],a=1/255*Math.min(t,Math.min(i,s));return s=1-1/255*Math.max(t,Math.max(i,s)),[r,100*a,100*s]},n.rgb.cmyk=function(e){const t=e[0]/255,i=e[1]/255,s=e[2]/255,r=Math.min(1-t,1-i,1-s);return[100*((1-t-r)/(1-r)||0),100*((1-i-r)/(1-r)||0),100*((1-s-r)/(1-r)||0),100*r]},n.rgb.keyword=function(e){const t=r[e];if(t)return t;let i,n=1/0;for(const t of Object.keys(s)){const r=(o=s[t],((a=e)[0]-o[0])**2+(a[1]-o[1])**2+(a[2]-o[2])**2);r.04045?((t+.055)/1.055)**2.4:t/12.92,i=i>.04045?((i+.055)/1.055)**2.4:i/12.92,s=s>.04045?((s+.055)/1.055)**2.4:s/12.92,[100*(.4124*t+.3576*i+.1805*s),100*(.2126*t+.7152*i+.0722*s),100*(.0193*t+.1192*i+.9505*s)]},n.rgb.lab=function(e){const t=n.rgb.xyz(e);let i=t[0],s=t[1],r=t[2];return i/=95.047,s/=100,r/=108.883,i=i>.008856?i**(1/3):7.787*i+16/116,s=s>.008856?s**(1/3):7.787*s+16/116,r=r>.008856?r**(1/3):7.787*r+16/116,[116*s-16,500*(i-s),200*(s-r)]},n.hsl.rgb=function(e){const t=e[0]/360,i=e[1]/100,s=e[2]/100;let r,n,a;if(0===i)return a=255*s,[a,a,a];r=s<.5?s*(1+i):s+i-s*i;const o=2*s-r,c=[0,0,0];for(let e=0;e<3;e++)n=t+1/3*-(e-1),n<0&&n++,n>1&&n--,a=6*n<1?o+6*(r-o)*n:2*n<1?r:3*n<2?o+(r-o)*(2/3-n)*6:o,c[e]=255*a;return c},n.hsl.hsv=function(e){const t=e[0];let i=e[1]/100,s=e[2]/100,r=i;const n=Math.max(s,.01);return s*=2,i*=s<=1?s:2-s,r*=n<=1?n:2-n,[t,100*(0===s?2*r/(n+r):2*i/(s+i)),(s+i)/2*100]},n.hsv.rgb=function(e){const t=e[0]/60,i=e[1]/100;let s=e[2]/100;const r=Math.floor(t)%6,n=t-Math.floor(t),a=255*s*(1-i),o=255*s*(1-i*n),c=255*s*(1-i*(1-n));switch(s*=255,r){case 0:return[s,c,a];case 1:return[o,s,a];case 2:return[a,s,c];case 3:return[a,o,s];case 4:return[c,a,s];case 5:return[s,a,o]}},n.hsv.hsl=function(e){const t=e[0],i=e[1]/100,s=e[2]/100,r=Math.max(s,.01);let n,a;a=(2-i)*s;const o=(2-i)*r;return n=i*r,n/=o<=1?o:2-o,n=n||0,a/=2,[t,100*n,100*a]},n.hwb.rgb=function(e){const t=e[0]/360;let i=e[1]/100,s=e[2]/100;const r=i+s;let n;r>1&&(i/=r,s/=r);const a=Math.floor(6*t),o=1-s;n=6*t-a,0!=(1&a)&&(n=1-n);const c=i+n*(o-i);let l,p,A;switch(a){default:case 6:case 0:l=o,p=c,A=i;break;case 1:l=c,p=o,A=i;break;case 2:l=i,p=o,A=c;break;case 3:l=i,p=c,A=o;break;case 4:l=c,p=i,A=o;break;case 5:l=o,p=i,A=c}return[255*l,255*p,255*A]},n.cmyk.rgb=function(e){const t=e[0]/100,i=e[1]/100,s=e[2]/100,r=e[3]/100;return[255*(1-Math.min(1,t*(1-r)+r)),255*(1-Math.min(1,i*(1-r)+r)),255*(1-Math.min(1,s*(1-r)+r))]},n.xyz.rgb=function(e){const t=e[0]/100,i=e[1]/100,s=e[2]/100;let r,n,a;return r=3.2406*t+-1.5372*i+-.4986*s,n=-.9689*t+1.8758*i+.0415*s,a=.0557*t+-.204*i+1.057*s,r=r>.0031308?1.055*r**(1/2.4)-.055:12.92*r,n=n>.0031308?1.055*n**(1/2.4)-.055:12.92*n,a=a>.0031308?1.055*a**(1/2.4)-.055:12.92*a,r=Math.min(Math.max(0,r),1),n=Math.min(Math.max(0,n),1),a=Math.min(Math.max(0,a),1),[255*r,255*n,255*a]},n.xyz.lab=function(e){let t=e[0],i=e[1],s=e[2];return t/=95.047,i/=100,s/=108.883,t=t>.008856?t**(1/3):7.787*t+16/116,i=i>.008856?i**(1/3):7.787*i+16/116,s=s>.008856?s**(1/3):7.787*s+16/116,[116*i-16,500*(t-i),200*(i-s)]},n.lab.xyz=function(e){let t,i,s;i=(e[0]+16)/116,t=e[1]/500+i,s=i-e[2]/200;const r=i**3,n=t**3,a=s**3;return i=r>.008856?r:(i-16/116)/7.787,t=n>.008856?n:(t-16/116)/7.787,s=a>.008856?a:(s-16/116)/7.787,t*=95.047,i*=100,s*=108.883,[t,i,s]},n.lab.lch=function(e){const t=e[0],i=e[1],s=e[2];let r;return r=360*Math.atan2(s,i)/2/Math.PI,r<0&&(r+=360),[t,Math.sqrt(i*i+s*s),r]},n.lch.lab=function(e){const t=e[0],i=e[1],s=e[2]/360*2*Math.PI;return[t,i*Math.cos(s),i*Math.sin(s)]},n.rgb.ansi16=function(e,t=null){const[i,s,r]=e;let a=null===t?n.rgb.hsv(e)[2]:t;if(a=Math.round(a/50),0===a)return 30;let o=30+(Math.round(r/255)<<2|Math.round(s/255)<<1|Math.round(i/255));return 2===a&&(o+=60),o},n.hsv.ansi16=function(e){return n.rgb.ansi16(n.hsv.rgb(e),e[2])},n.rgb.ansi256=function(e){const t=e[0],i=e[1],s=e[2];return t===i&&i===s?t<8?16:t>248?231:Math.round((t-8)/247*24)+232:16+36*Math.round(t/255*5)+6*Math.round(i/255*5)+Math.round(s/255*5)},n.ansi16.rgb=function(e){let t=e%10;if(0===t||7===t)return e>50&&(t+=3.5),t=t/10.5*255,[t,t,t];const i=.5*(1+~~(e>50));return[(1&t)*i*255,(t>>1&1)*i*255,(t>>2&1)*i*255]},n.ansi256.rgb=function(e){if(e>=232){const t=10*(e-232)+8;return[t,t,t]}let t;return e-=16,[Math.floor(e/36)/5*255,Math.floor((t=e%36)/6)/5*255,t%6/5*255]},n.rgb.hex=function(e){const t=(((255&Math.round(e[0]))<<16)+((255&Math.round(e[1]))<<8)+(255&Math.round(e[2]))).toString(16).toUpperCase();return"000000".substring(t.length)+t},n.hex.rgb=function(e){const t=e.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!t)return[0,0,0];let i=t[0];3===t[0].length&&(i=i.split("").map((e=>e+e)).join(""));const s=parseInt(i,16);return[s>>16&255,s>>8&255,255&s]},n.rgb.hcg=function(e){const t=e[0]/255,i=e[1]/255,s=e[2]/255,r=Math.max(Math.max(t,i),s),n=Math.min(Math.min(t,i),s),a=r-n;let o,c;return o=a<1?n/(1-a):0,c=a<=0?0:r===t?(i-s)/a%6:r===i?2+(s-t)/a:4+(t-i)/a,c/=6,c%=1,[360*c,100*a,100*o]},n.hsl.hcg=function(e){const t=e[1]/100,i=e[2]/100,s=i<.5?2*t*i:2*t*(1-i);let r=0;return s<1&&(r=(i-.5*s)/(1-s)),[e[0],100*s,100*r]},n.hsv.hcg=function(e){const t=e[1]/100,i=e[2]/100,s=t*i;let r=0;return s<1&&(r=(i-s)/(1-s)),[e[0],100*s,100*r]},n.hcg.rgb=function(e){const t=e[0]/360,i=e[1]/100,s=e[2]/100;if(0===i)return[255*s,255*s,255*s];const r=[0,0,0],n=t%1*6,a=n%1,o=1-a;let c=0;switch(Math.floor(n)){case 0:r[0]=1,r[1]=a,r[2]=0;break;case 1:r[0]=o,r[1]=1,r[2]=0;break;case 2:r[0]=0,r[1]=1,r[2]=a;break;case 3:r[0]=0,r[1]=o,r[2]=1;break;case 4:r[0]=a,r[1]=0,r[2]=1;break;default:r[0]=1,r[1]=0,r[2]=o}return c=(1-i)*s,[255*(i*r[0]+c),255*(i*r[1]+c),255*(i*r[2]+c)]},n.hcg.hsv=function(e){const t=e[1]/100,i=t+e[2]/100*(1-t);let s=0;return i>0&&(s=t/i),[e[0],100*s,100*i]},n.hcg.hsl=function(e){const t=e[1]/100,i=e[2]/100*(1-t)+.5*t;let s=0;return i>0&&i<.5?s=t/(2*i):i>=.5&&i<1&&(s=t/(2*(1-i))),[e[0],100*s,100*i]},n.hcg.hwb=function(e){const t=e[1]/100,i=t+e[2]/100*(1-t);return[e[0],100*(i-t),100*(1-i)]},n.hwb.hcg=function(e){const t=e[1]/100,i=1-e[2]/100,s=i-t;let r=0;return s<1&&(r=(i-s)/(1-s)),[e[0],100*s,100*r]},n.apple.rgb=function(e){return[e[0]/65535*255,e[1]/65535*255,e[2]/65535*255]},n.rgb.apple=function(e){return[e[0]/255*65535,e[1]/255*65535,e[2]/255*65535]},n.gray.rgb=function(e){return[e[0]/100*255,e[0]/100*255,e[0]/100*255]},n.gray.hsl=function(e){return[0,0,e[0]]},n.gray.hsv=n.gray.hsl,n.gray.hwb=function(e){return[0,100,e[0]]},n.gray.cmyk=function(e){return[0,0,0,e[0]]},n.gray.lab=function(e){return[e[0],0,0]},n.gray.hex=function(e){const t=255&Math.round(e[0]/100*255),i=((t<<16)+(t<<8)+t).toString(16).toUpperCase();return"000000".substring(i.length)+i},n.rgb.gray=function(e){return[(e[0]+e[1]+e[2])/3/255*100]}},60398:(e,t,i)=>{const s=i(38097),r=i(14657),n={};Object.keys(s).forEach((e=>{n[e]={},Object.defineProperty(n[e],"channels",{value:s[e].channels}),Object.defineProperty(n[e],"labels",{value:s[e].labels});const t=r(e);Object.keys(t).forEach((i=>{const s=t[i];n[e][i]=function(e){const t=function(...t){const i=t[0];if(null==i)return i;i.length>1&&(t=i);const s=e(t);if("object"==typeof s)for(let e=s.length,t=0;t1&&(t=i),e(t))};return"conversion"in e&&(t.conversion=e.conversion),t}(s)}))})),e.exports=n},14657:(e,t,i)=>{const s=i(38097);function r(e,t){return function(i){return t(e(i))}}function n(e,t){const i=[t[e].parent,e];let n=s[t[e].parent][e],a=t[e].parent;for(;t[a].parent;)i.unshift(t[a].parent),n=r(s[t[a].parent][a],n),a=t[a].parent;return n.conversion=i,n}e.exports=function(e){const t=function(e){const t=function(){const e={},t=Object.keys(s);for(let i=t.length,s=0;s{"use strict";e.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},92604:(e,t,i)=>{var s={};e.exports=s,s.themes={};var r=s.styles=i(76375),n=Object.defineProperties;s.supportsColor=i(33578),void 0===s.enabled&&(s.enabled=s.supportsColor),s.stripColors=s.strip=function(e){return(""+e).replace(/\x1B\[\d+m/g,"")},s.stylize=function(e,t){return r[t].open+e+r[t].close};var a=/[|\\{}()[\]^$+*?.]/g;function o(e){var t=function e(){return A.apply(e,arguments)};return t._styles=e,t.__proto__=p,t}var c,l=(c={},r.grey=r.gray,Object.keys(r).forEach((function(e){r[e].closeRe=new RegExp(function(e){if("string"!=typeof e)throw new TypeError("Expected a string");return e.replace(a,"\\$&")}(r[e].close),"g"),c[e]={get:function(){return o(this._styles.concat(e))}}})),c),p=n((function(){}),l);function A(){var e=arguments,t=e.length,i=0!==t&&String(arguments[0]);if(t>1)for(var n=1;n{e.exports=function(e,t){var i="";e=(e=e||"Run the trap, drop the bass").split("");var s={a:["@","Ą","Ⱥ","Ʌ","Δ","Λ","Д"],b:["ß","Ɓ","Ƀ","ɮ","β","฿"],c:["©","Ȼ","Ͼ"],d:["Ð","Ɗ","Ԁ","ԁ","Ԃ","ԃ"],e:["Ë","ĕ","Ǝ","ɘ","Σ","ξ","Ҽ","੬"],f:["Ӻ"],g:["ɢ"],h:["Ħ","ƕ","Ң","Һ","Ӈ","Ԋ"],i:["༏"],j:["Ĵ"],k:["ĸ","Ҡ","Ӄ","Ԟ"],l:["Ĺ"],m:["ʍ","Ӎ","ӎ","Ԡ","ԡ","൩"],n:["Ñ","ŋ","Ɲ","Ͷ","Π","Ҋ"],o:["Ø","õ","ø","Ǿ","ʘ","Ѻ","ם","۝","๏"],p:["Ƿ","Ҏ"],q:["্"],r:["®","Ʀ","Ȑ","Ɍ","ʀ","Я"],s:["§","Ϟ","ϟ","Ϩ"],t:["Ł","Ŧ","ͳ"],u:["Ʊ","Ս"],v:["ט"],w:["Ш","Ѡ","Ѽ","൰"],x:["Ҳ","Ӿ","Ӽ","ӽ"],y:["¥","Ұ","Ӌ"],z:["Ƶ","ɀ"]};return e.forEach((function(e){e=e.toLowerCase();var t=s[e]||[" "],r=Math.floor(Math.random()*t.length);i+=void 0!==s[e]?s[e][r]:e})),i}},16893:e=>{e.exports=function(e,t){e=e||" he is here ";var i={up:["̍","̎","̄","̅","̿","̑","̆","̐","͒","͗","͑","̇","̈","̊","͂","̓","̈","͊","͋","͌","̃","̂","̌","͐","̀","́","̋","̏","̒","̓","̔","̽","̉","ͣ","ͤ","ͥ","ͦ","ͧ","ͨ","ͩ","ͪ","ͫ","ͬ","ͭ","ͮ","ͯ","̾","͛","͆","̚"],down:["̖","̗","̘","̙","̜","̝","̞","̟","̠","̤","̥","̦","̩","̪","̫","̬","̭","̮","̯","̰","̱","̲","̳","̹","̺","̻","̼","ͅ","͇","͈","͉","͍","͎","͓","͔","͕","͖","͙","͚","̣"],mid:["̕","̛","̀","́","͘","̡","̢","̧","̨","̴","̵","̶","͜","͝","͞","͟","͠","͢","̸","̷","͡"," ҉"]},s=[].concat(i.up,i.down,i.mid);function r(e){return Math.floor(Math.random()*e)}function n(e){var t=!1;return s.filter((function(i){t=i===e})),t}return function(e,t){var s,a,o="";for(a in(t=t||{}).up=t.up||!0,t.mid=t.mid||!0,t.down=t.down||!0,t.size=t.size||"maxi",e=e.split(""))if(!n(a)){switch(o+=e[a],s={up:0,down:0,mid:0},t.size){case"mini":s.up=r(8),s.min=r(2),s.down=r(8);break;case"maxi":s.up=r(16)+3,s.min=r(4)+1,s.down=r(64)+3;break;default:s.up=r(8)+1,s.mid=r(6)/2,s.down=r(8)+1}var c=["up","mid","down"];for(var l in c)for(var p=c[l],A=0;A<=s[p];A++)t[p]&&(o+=i[p][r(i[p].length)])}return o}(e)}},70418:(e,t,i)=>{var s=i(92604);e.exports=function(e,t,i){if(" "===e)return e;switch(t%3){case 0:return s.red(e);case 1:return s.white(e);case 2:return s.blue(e)}}},22430:(e,t,i)=>{var s,r=i(92604);e.exports=(s=["red","yellow","green","blue","magenta"],function(e,t,i){return" "===e?e:r[s[t++%s.length]](e)})},35041:(e,t,i)=>{var s,r=i(92604);e.exports=(s=["underline","inverse","grey","yellow","red","green","blue","white","cyan","magenta"],function(e,t,i){return" "===e?e:r[s[Math.round(Math.random()*(s.length-1))]](e)})},76492:(e,t,i)=>{var s=i(92604);e.exports=function(e,t,i){return t%2==0?e:s.inverse(e)}},76375:e=>{var t={};e.exports=t;var i={reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29],black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],gray:[90,39],grey:[90,39],bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],blackBG:[40,49],redBG:[41,49],greenBG:[42,49],yellowBG:[43,49],blueBG:[44,49],magentaBG:[45,49],cyanBG:[46,49],whiteBG:[47,49]};Object.keys(i).forEach((function(e){var s=i[e],r=t[e]=[];r.open="["+s[0]+"m",r.close="["+s[1]+"m"}))},33578:e=>{var t=process.argv;e.exports=-1===t.indexOf("--no-color")&&-1===t.indexOf("--color=false")&&(-1!==t.indexOf("--color")||-1!==t.indexOf("--color=true")||-1!==t.indexOf("--color=always")||!(process.stdout&&!process.stdout.isTTY)&&("win32"===process.platform||"COLORTERM"in process.env||"dumb"!==process.env.TERM&&!!/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)))},28130:e=>{function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=28130,e.exports=t},27072:(e,t,i)=>{var s=i(92604);e.exports=s},14598:(e,t,i)=>{var s=i(73837),r=i(12781).Stream,n=i(65239);function a(){this.writable=!1,this.readable=!0,this.dataSize=0,this.maxDataSize=2097152,this.pauseStreams=!0,this._released=!1,this._streams=[],this._currentStream=null,this._insideLoop=!1,this._pendingNext=!1}e.exports=a,s.inherits(a,r),a.create=function(e){var t=new this;for(var i in e=e||{})t[i]=e[i];return t},a.isStreamLike=function(e){return"function"!=typeof e&&"string"!=typeof e&&"boolean"!=typeof e&&"number"!=typeof e&&!Buffer.isBuffer(e)},a.prototype.append=function(e){if(a.isStreamLike(e)){if(!(e instanceof n)){var t=n.create(e,{maxDataSize:1/0,pauseStream:this.pauseStreams});e.on("data",this._checkDataSize.bind(this)),e=t}this._handleErrors(e),this.pauseStreams&&e.pause()}return this._streams.push(e),this},a.prototype.pipe=function(e,t){return r.prototype.pipe.call(this,e,t),this.resume(),e},a.prototype._getNext=function(){if(this._currentStream=null,this._insideLoop)this._pendingNext=!0;else{this._insideLoop=!0;try{do{this._pendingNext=!1,this._realGetNext()}while(this._pendingNext)}finally{this._insideLoop=!1}}},a.prototype._realGetNext=function(){var e=this._streams.shift();void 0!==e?"function"==typeof e?e(function(e){a.isStreamLike(e)&&(e.on("data",this._checkDataSize.bind(this)),this._handleErrors(e)),this._pipeNext(e)}.bind(this)):this._pipeNext(e):this.end()},a.prototype._pipeNext=function(e){if(this._currentStream=e,a.isStreamLike(e))return e.on("end",this._getNext.bind(this)),void e.pipe(this,{end:!1});var t=e;this.write(t),this._getNext()},a.prototype._handleErrors=function(e){var t=this;e.on("error",(function(e){t._emitError(e)}))},a.prototype.write=function(e){this.emit("data",e)},a.prototype.pause=function(){this.pauseStreams&&(this.pauseStreams&&this._currentStream&&"function"==typeof this._currentStream.pause&&this._currentStream.pause(),this.emit("pause"))},a.prototype.resume=function(){this._released||(this._released=!0,this.writable=!0,this._getNext()),this.pauseStreams&&this._currentStream&&"function"==typeof this._currentStream.resume&&this._currentStream.resume(),this.emit("resume")},a.prototype.end=function(){this._reset(),this.emit("end")},a.prototype.destroy=function(){this._reset(),this.emit("close")},a.prototype._reset=function(){this.writable=!1,this._streams=[],this._currentStream=null},a.prototype._checkDataSize=function(){if(this._updateDataSize(),!(this.dataSize<=this.maxDataSize)){var e="DelayedStream#maxDataSize of "+this.maxDataSize+" bytes exceeded.";this._emitError(new Error(e))}},a.prototype._updateDataSize=function(){this.dataSize=0;var e=this;this._streams.forEach((function(t){t.dataSize&&(e.dataSize+=t.dataSize)})),this._currentStream&&this._currentStream.dataSize&&(this.dataSize+=this._currentStream.dataSize)},a.prototype._emitError=function(e){this._reset(),this.emit("error",e)}},95146:(e,t,i)=>{t.formatArgs=function(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff),!this.useColors)return;const i="color: "+this.color;t.splice(1,0,i,"color: inherit");let s=0,r=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{"%%"!==e&&(s++,"%c"===e&&(r=s))})),t.splice(r,0,i)},t.save=function(e){try{e?t.storage.setItem("debug",e):t.storage.removeItem("debug")}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem("debug")}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e},t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||(()=>{}),e.exports=i(17498)(t);const{formatters:s}=e.exports;s.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},17498:(e,t,i)=>{e.exports=function(e){function t(e){let i,r,n,a=null;function o(...e){if(!o.enabled)return;const s=o,r=Number(new Date),n=r-(i||r);s.diff=n,s.prev=i,s.curr=r,i=r,e[0]=t.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let a=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,((i,r)=>{if("%%"===i)return"%";a++;const n=t.formatters[r];if("function"==typeof n){const t=e[a];i=n.call(s,t),e.splice(a,1),a--}return i})),t.formatArgs.call(s,e),(s.log||t.log).apply(s,e)}return o.namespace=e,o.useColors=t.useColors(),o.color=t.selectColor(e),o.extend=s,o.destroy=t.destroy,Object.defineProperty(o,"enabled",{enumerable:!0,configurable:!1,get:()=>null!==a?a:(r!==t.namespaces&&(r=t.namespaces,n=t.enabled(e)),n),set:e=>{a=e}}),"function"==typeof t.init&&t.init(o),o}function s(e,i){const s=t(this.namespace+(void 0===i?":":i)+e);return s.log=this.log,s}function r(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return t.debug=t,t.default=t,t.coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){const e=[...t.names.map(r),...t.skips.map(r).map((e=>"-"+e))].join(",");return t.enable(""),e},t.enable=function(e){let i;t.save(e),t.namespaces=e,t.names=[],t.skips=[];const s=("string"==typeof e?e:"").split(/[\s,]+/),r=s.length;for(i=0;i{t[i]=e[i]})),t.names=[],t.skips=[],t.formatters={},t.selectColor=function(e){let i=0;for(let t=0;t{"undefined"==typeof process||"renderer"===process.type||!0===process.browser||process.__nwjs?e.exports=i(95146):e.exports=i(46072)},46072:(e,t,i)=>{const s=i(76224),r=i(73837);t.init=function(e){e.inspectOpts={};const i=Object.keys(t.inspectOpts);for(let s=0;s{}),"Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."),t.colors=[6,2,3,4,5,1];try{const e=i(56778);e&&(e.stderr||e).level>=2&&(t.colors=[20,21,26,27,32,33,38,39,40,41,42,43,44,45,56,57,62,63,68,69,74,75,76,77,78,79,80,81,92,93,98,99,112,113,128,129,134,135,148,149,160,161,162,163,164,165,166,167,168,169,170,171,172,173,178,179,184,185,196,197,198,199,200,201,202,203,204,205,206,207,208,209,214,215,220,221])}catch(e){}t.inspectOpts=Object.keys(process.env).filter((e=>/^debug_/i.test(e))).reduce(((e,t)=>{const i=t.substring(6).toLowerCase().replace(/_([a-z])/g,((e,t)=>t.toUpperCase()));let s=process.env[t];return s=!!/^(yes|on|true|enabled)$/i.test(s)||!/^(no|off|false|disabled)$/i.test(s)&&("null"===s?null:Number(s)),e[i]=s,e}),{}),e.exports=i(17498)(t);const{formatters:n}=e.exports;n.o=function(e){return this.inspectOpts.colors=this.useColors,r.inspect(e,this.inspectOpts).split("\n").map((e=>e.trim())).join(" ")},n.O=function(e){return this.inspectOpts.colors=this.useColors,r.inspect(e,this.inspectOpts)}},70596:(e,t,i)=>{var s=i(54256);e.exports=function(e,t){return e=e||{},Object.keys(t).forEach((function(i){void 0===e[i]&&(e[i]=s(t[i]))})),e}},65239:(e,t,i)=>{var s=i(12781).Stream,r=i(73837);function n(){this.source=null,this.dataSize=0,this.maxDataSize=1048576,this.pauseStream=!0,this._maxDataSizeExceeded=!1,this._released=!1,this._bufferedEvents=[]}e.exports=n,r.inherits(n,s),n.create=function(e,t){var i=new this;for(var s in t=t||{})i[s]=t[s];i.source=e;var r=e.emit;return e.emit=function(){return i._handleEmit(arguments),r.apply(e,arguments)},e.on("error",(function(){})),i.pauseStream&&e.pause(),i},Object.defineProperty(n.prototype,"readable",{configurable:!0,enumerable:!0,get:function(){return this.source.readable}}),n.prototype.setEncoding=function(){return this.source.setEncoding.apply(this.source,arguments)},n.prototype.resume=function(){this._released||this.release(),this.source.resume()},n.prototype.pause=function(){this.source.pause()},n.prototype.release=function(){this._released=!0,this._bufferedEvents.forEach(function(e){this.emit.apply(this,e)}.bind(this)),this._bufferedEvents=[]},n.prototype.pipe=function(){var e=s.prototype.pipe.apply(this,arguments);return this.resume(),e},n.prototype._handleEmit=function(e){this._released?this.emit.apply(this,e):("data"===e[0]&&(this.dataSize+=e[1].length,this._checkIfMaxDataSizeExceeded()),this._bufferedEvents.push(e))},n.prototype._checkIfMaxDataSizeExceeded=function(){if(!(this._maxDataSizeExceeded||this.dataSize<=this.maxDataSize)){this._maxDataSizeExceeded=!0;var e="DelayedStream#maxDataSize of "+this.maxDataSize+" bytes exceeded.";this.emit("error",new Error(e))}}},57751:(e,t,i)=>{"use strict";i.d(t,{$:()=>s});class s extends Error{constructor(e){super(e),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name="Deprecation"}}},90772:(e,t,i)=>{const s=i(57147),r=i(71017),n=i(22037);function a(e){console.log(`[dotenv][DEBUG] ${e}`)}const o=/^\s*([\w.-]+)\s*=\s*(.*)?\s*$/,c=/\\n/g,l=/\r\n|\n|\r/;function p(e,t){const i=Boolean(t&&t.debug),s={};return e.toString().split(l).forEach((function(e,t){const r=e.match(o);if(null!=r){const e=r[1];let t=r[2]||"";const i=t.length-1,n='"'===t[0]&&'"'===t[i];"'"===t[0]&&"'"===t[i]||n?(t=t.substring(1,i),n&&(t=t.replace(c,"\n"))):t=t.trim(),s[e]=t}else i&&a(`did not match key and value when parsing line ${t+1}: ${e}`)})),s}e.exports.config=function(e){let t=r.resolve(process.cwd(),".env"),i="utf8",o=!1;var c;e&&(null!=e.path&&(t="~"===(c=e.path)[0]?r.join(n.homedir(),c.slice(1)):c),null!=e.encoding&&(i=e.encoding),null!=e.debug&&(o=!0));try{const e=p(s.readFileSync(t,{encoding:i}),{debug:o});return Object.keys(e).forEach((function(t){Object.prototype.hasOwnProperty.call(process.env,t)?o&&a(`"${t}" is already defined in \`process.env\` and will not be overwritten`):process.env[t]=e[t]})),{parsed:e}}catch(e){return{error:e}}},e.exports.parse=p},87491:function(e){var t;t=function(){return function(e){var t={};function i(s){if(t[s])return t[s].exports;var r=t[s]={exports:{},id:s,loaded:!1};return e[s].call(r.exports,r,r.exports,i),r.loaded=!0,r.exports}return i.m=e,i.c=t,i.p="",i(0)}([function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var s=i(1),r=i(3),n=i(8),a=i(15);function o(e,t,i){var a=null,o=function(e,t){i&&i(e,t),a&&a.visit(e,t)},c="function"==typeof i?o:null,l=!1;if(t){l="boolean"==typeof t.comment&&t.comment;var p="boolean"==typeof t.attachComment&&t.attachComment;(l||p)&&((a=new s.CommentHandler).attach=p,t.comment=!0,c=o)}var A,u=!1;t&&"string"==typeof t.sourceType&&(u="module"===t.sourceType),A=t&&"boolean"==typeof t.jsx&&t.jsx?new r.JSXParser(e,t,c):new n.Parser(e,t,c);var d=u?A.parseModule():A.parseScript();return l&&a&&(d.comments=a.comments),A.config.tokens&&(d.tokens=A.tokens),A.config.tolerant&&(d.errors=A.errorHandler.errors),d}t.parse=o,t.parseModule=function(e,t,i){var s=t||{};return s.sourceType="module",o(e,s,i)},t.parseScript=function(e,t,i){var s=t||{};return s.sourceType="script",o(e,s,i)},t.tokenize=function(e,t,i){var s,r=new a.Tokenizer(e,t);s=[];try{for(;;){var n=r.getNextToken();if(!n)break;i&&(n=i(n)),s.push(n)}}catch(e){r.errorHandler.tolerate(e)}return r.errorHandler.tolerant&&(s.errors=r.errors()),s};var c=i(2);t.Syntax=c.Syntax,t.version="4.0.1"},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var s=i(2),r=function(){function e(){this.attach=!1,this.comments=[],this.stack=[],this.leading=[],this.trailing=[]}return e.prototype.insertInnerComments=function(e,t){if(e.type===s.Syntax.BlockStatement&&0===e.body.length){for(var i=[],r=this.leading.length-1;r>=0;--r){var n=this.leading[r];t.end.offset>=n.start&&(i.unshift(n.comment),this.leading.splice(r,1),this.trailing.splice(r,1))}i.length&&(e.innerComments=i)}},e.prototype.findTrailingComments=function(e){var t=[];if(this.trailing.length>0){for(var i=this.trailing.length-1;i>=0;--i){var s=this.trailing[i];s.start>=e.end.offset&&t.unshift(s.comment)}return this.trailing.length=0,t}var r=this.stack[this.stack.length-1];if(r&&r.node.trailingComments){var n=r.node.trailingComments[0];n&&n.range[0]>=e.end.offset&&(t=r.node.trailingComments,delete r.node.trailingComments)}return t},e.prototype.findLeadingComments=function(e){for(var t,i=[];this.stack.length>0&&(n=this.stack[this.stack.length-1])&&n.start>=e.start.offset;)t=n.node,this.stack.pop();if(t){for(var s=(t.leadingComments?t.leadingComments.length:0)-1;s>=0;--s){var r=t.leadingComments[s];r.range[1]<=e.start.offset&&(i.unshift(r),t.leadingComments.splice(s,1))}return t.leadingComments&&0===t.leadingComments.length&&delete t.leadingComments,i}for(s=this.leading.length-1;s>=0;--s){var n;(n=this.leading[s]).start<=e.start.offset&&(i.unshift(n.comment),this.leading.splice(s,1))}return i},e.prototype.visitNode=function(e,t){if(!(e.type===s.Syntax.Program&&e.body.length>0)){this.insertInnerComments(e,t);var i=this.findTrailingComments(t),r=this.findLeadingComments(t);r.length>0&&(e.leadingComments=r),i.length>0&&(e.trailingComments=i),this.stack.push({node:e,start:t.start.offset})}},e.prototype.visitComment=function(e,t){var i="L"===e.type[0]?"Line":"Block",s={type:i,value:e.value};if(e.range&&(s.range=e.range),e.loc&&(s.loc=e.loc),this.comments.push(s),this.attach){var r={comment:{type:i,value:e.value,range:[t.start.offset,t.end.offset]},start:t.start.offset};e.loc&&(r.comment.loc=e.loc),e.type=i,this.leading.push(r),this.trailing.push(r)}},e.prototype.visit=function(e,t){"LineComment"===e.type||"BlockComment"===e.type?this.visitComment(e,t):this.attach&&this.visitNode(e,t)},e}();t.CommentHandler=r},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Syntax={AssignmentExpression:"AssignmentExpression",AssignmentPattern:"AssignmentPattern",ArrayExpression:"ArrayExpression",ArrayPattern:"ArrayPattern",ArrowFunctionExpression:"ArrowFunctionExpression",AwaitExpression:"AwaitExpression",BlockStatement:"BlockStatement",BinaryExpression:"BinaryExpression",BreakStatement:"BreakStatement",CallExpression:"CallExpression",CatchClause:"CatchClause",ClassBody:"ClassBody",ClassDeclaration:"ClassDeclaration",ClassExpression:"ClassExpression",ConditionalExpression:"ConditionalExpression",ContinueStatement:"ContinueStatement",DoWhileStatement:"DoWhileStatement",DebuggerStatement:"DebuggerStatement",EmptyStatement:"EmptyStatement",ExportAllDeclaration:"ExportAllDeclaration",ExportDefaultDeclaration:"ExportDefaultDeclaration",ExportNamedDeclaration:"ExportNamedDeclaration",ExportSpecifier:"ExportSpecifier",ExpressionStatement:"ExpressionStatement",ForStatement:"ForStatement",ForOfStatement:"ForOfStatement",ForInStatement:"ForInStatement",FunctionDeclaration:"FunctionDeclaration",FunctionExpression:"FunctionExpression",Identifier:"Identifier",IfStatement:"IfStatement",ImportDeclaration:"ImportDeclaration",ImportDefaultSpecifier:"ImportDefaultSpecifier",ImportNamespaceSpecifier:"ImportNamespaceSpecifier",ImportSpecifier:"ImportSpecifier",Literal:"Literal",LabeledStatement:"LabeledStatement",LogicalExpression:"LogicalExpression",MemberExpression:"MemberExpression",MetaProperty:"MetaProperty",MethodDefinition:"MethodDefinition",NewExpression:"NewExpression",ObjectExpression:"ObjectExpression",ObjectPattern:"ObjectPattern",Program:"Program",Property:"Property",RestElement:"RestElement",ReturnStatement:"ReturnStatement",SequenceExpression:"SequenceExpression",SpreadElement:"SpreadElement",Super:"Super",SwitchCase:"SwitchCase",SwitchStatement:"SwitchStatement",TaggedTemplateExpression:"TaggedTemplateExpression",TemplateElement:"TemplateElement",TemplateLiteral:"TemplateLiteral",ThisExpression:"ThisExpression",ThrowStatement:"ThrowStatement",TryStatement:"TryStatement",UnaryExpression:"UnaryExpression",UpdateExpression:"UpdateExpression",VariableDeclaration:"VariableDeclaration",VariableDeclarator:"VariableDeclarator",WhileStatement:"WhileStatement",WithStatement:"WithStatement",YieldExpression:"YieldExpression"}},function(e,t,i){"use strict";var s,r=this&&this.__extends||(s=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)t.hasOwnProperty(i)&&(e[i]=t[i])},function(e,t){function i(){this.constructor=e}s(e,t),e.prototype=null===t?Object.create(t):(i.prototype=t.prototype,new i)});Object.defineProperty(t,"__esModule",{value:!0});var n=i(4),a=i(5),o=i(6),c=i(7),l=i(8),p=i(13),A=i(14);function u(e){var t;switch(e.type){case o.JSXSyntax.JSXIdentifier:t=e.name;break;case o.JSXSyntax.JSXNamespacedName:var i=e;t=u(i.namespace)+":"+u(i.name);break;case o.JSXSyntax.JSXMemberExpression:var s=e;t=u(s.object)+"."+u(s.property)}return t}p.TokenName[100]="JSXIdentifier",p.TokenName[101]="JSXText";var d=function(e){function t(t,i,s){return e.call(this,t,i,s)||this}return r(t,e),t.prototype.parsePrimaryExpression=function(){return this.match("<")?this.parseJSXRoot():e.prototype.parsePrimaryExpression.call(this)},t.prototype.startJSX=function(){this.scanner.index=this.startMarker.index,this.scanner.lineNumber=this.startMarker.line,this.scanner.lineStart=this.startMarker.index-this.startMarker.column},t.prototype.finishJSX=function(){this.nextToken()},t.prototype.reenterJSX=function(){this.startJSX(),this.expectJSX("}"),this.config.tokens&&this.tokens.pop()},t.prototype.createJSXNode=function(){return this.collectComments(),{index:this.scanner.index,line:this.scanner.lineNumber,column:this.scanner.index-this.scanner.lineStart}},t.prototype.createJSXChildNode=function(){return{index:this.scanner.index,line:this.scanner.lineNumber,column:this.scanner.index-this.scanner.lineStart}},t.prototype.scanXHTMLEntity=function(e){for(var t="&",i=!0,s=!1,r=!1,a=!1;!this.scanner.eof()&&i&&!s;){var o=this.scanner.source[this.scanner.index];if(o===e)break;if(s=";"===o,t+=o,++this.scanner.index,!s)switch(t.length){case 2:r="#"===o;break;case 3:r&&(i=(a="x"===o)||n.Character.isDecimalDigit(o.charCodeAt(0)),r=r&&!a);break;default:i=(i=i&&!(r&&!n.Character.isDecimalDigit(o.charCodeAt(0))))&&!(a&&!n.Character.isHexDigit(o.charCodeAt(0)))}}if(i&&s&&t.length>2){var c=t.substr(1,t.length-2);r&&c.length>1?t=String.fromCharCode(parseInt(c.substr(1),10)):a&&c.length>2?t=String.fromCharCode(parseInt("0"+c.substr(1),16)):r||a||!A.XHTMLEntities[c]||(t=A.XHTMLEntities[c])}return t},t.prototype.lexJSX=function(){var e=this.scanner.source.charCodeAt(this.scanner.index);if(60===e||62===e||47===e||58===e||61===e||123===e||125===e)return{type:7,value:o=this.scanner.source[this.scanner.index++],lineNumber:this.scanner.lineNumber,lineStart:this.scanner.lineStart,start:this.scanner.index-1,end:this.scanner.index};if(34===e||39===e){for(var t=this.scanner.index,i=this.scanner.source[this.scanner.index++],s="";!this.scanner.eof()&&(c=this.scanner.source[this.scanner.index++])!==i;)s+="&"===c?this.scanXHTMLEntity(i):c;return{type:8,value:s,lineNumber:this.scanner.lineNumber,lineStart:this.scanner.lineStart,start:t,end:this.scanner.index}}if(46===e){var r=this.scanner.source.charCodeAt(this.scanner.index+1),a=this.scanner.source.charCodeAt(this.scanner.index+2),o=46===r&&46===a?"...":".";return t=this.scanner.index,this.scanner.index+=o.length,{type:7,value:o,lineNumber:this.scanner.lineNumber,lineStart:this.scanner.lineStart,start:t,end:this.scanner.index}}if(96===e)return{type:10,value:"",lineNumber:this.scanner.lineNumber,lineStart:this.scanner.lineStart,start:this.scanner.index,end:this.scanner.index};if(n.Character.isIdentifierStart(e)&&92!==e){for(t=this.scanner.index,++this.scanner.index;!this.scanner.eof();){var c=this.scanner.source.charCodeAt(this.scanner.index);if(n.Character.isIdentifierPart(c)&&92!==c)++this.scanner.index;else{if(45!==c)break;++this.scanner.index}}return{type:100,value:this.scanner.source.slice(t,this.scanner.index),lineNumber:this.scanner.lineNumber,lineStart:this.scanner.lineStart,start:t,end:this.scanner.index}}return this.scanner.lex()},t.prototype.nextJSXToken=function(){this.collectComments(),this.startMarker.index=this.scanner.index,this.startMarker.line=this.scanner.lineNumber,this.startMarker.column=this.scanner.index-this.scanner.lineStart;var e=this.lexJSX();return this.lastMarker.index=this.scanner.index,this.lastMarker.line=this.scanner.lineNumber,this.lastMarker.column=this.scanner.index-this.scanner.lineStart,this.config.tokens&&this.tokens.push(this.convertToken(e)),e},t.prototype.nextJSXText=function(){this.startMarker.index=this.scanner.index,this.startMarker.line=this.scanner.lineNumber,this.startMarker.column=this.scanner.index-this.scanner.lineStart;for(var e=this.scanner.index,t="";!this.scanner.eof();){var i=this.scanner.source[this.scanner.index];if("{"===i||"<"===i)break;++this.scanner.index,t+=i,n.Character.isLineTerminator(i.charCodeAt(0))&&(++this.scanner.lineNumber,"\r"===i&&"\n"===this.scanner.source[this.scanner.index]&&++this.scanner.index,this.scanner.lineStart=this.scanner.index)}this.lastMarker.index=this.scanner.index,this.lastMarker.line=this.scanner.lineNumber,this.lastMarker.column=this.scanner.index-this.scanner.lineStart;var s={type:101,value:t,lineNumber:this.scanner.lineNumber,lineStart:this.scanner.lineStart,start:e,end:this.scanner.index};return t.length>0&&this.config.tokens&&this.tokens.push(this.convertToken(s)),s},t.prototype.peekJSXToken=function(){var e=this.scanner.saveState();this.scanner.scanComments();var t=this.lexJSX();return this.scanner.restoreState(e),t},t.prototype.expectJSX=function(e){var t=this.nextJSXToken();7===t.type&&t.value===e||this.throwUnexpectedToken(t)},t.prototype.matchJSX=function(e){var t=this.peekJSXToken();return 7===t.type&&t.value===e},t.prototype.parseJSXIdentifier=function(){var e=this.createJSXNode(),t=this.nextJSXToken();return 100!==t.type&&this.throwUnexpectedToken(t),this.finalize(e,new a.JSXIdentifier(t.value))},t.prototype.parseJSXElementName=function(){var e=this.createJSXNode(),t=this.parseJSXIdentifier();if(this.matchJSX(":")){var i=t;this.expectJSX(":");var s=this.parseJSXIdentifier();t=this.finalize(e,new a.JSXNamespacedName(i,s))}else if(this.matchJSX("."))for(;this.matchJSX(".");){var r=t;this.expectJSX(".");var n=this.parseJSXIdentifier();t=this.finalize(e,new a.JSXMemberExpression(r,n))}return t},t.prototype.parseJSXAttributeName=function(){var e,t=this.createJSXNode(),i=this.parseJSXIdentifier();if(this.matchJSX(":")){var s=i;this.expectJSX(":");var r=this.parseJSXIdentifier();e=this.finalize(t,new a.JSXNamespacedName(s,r))}else e=i;return e},t.prototype.parseJSXStringLiteralAttribute=function(){var e=this.createJSXNode(),t=this.nextJSXToken();8!==t.type&&this.throwUnexpectedToken(t);var i=this.getTokenRaw(t);return this.finalize(e,new c.Literal(t.value,i))},t.prototype.parseJSXExpressionAttribute=function(){var e=this.createJSXNode();this.expectJSX("{"),this.finishJSX(),this.match("}")&&this.tolerateError("JSX attributes must only be assigned a non-empty expression");var t=this.parseAssignmentExpression();return this.reenterJSX(),this.finalize(e,new a.JSXExpressionContainer(t))},t.prototype.parseJSXAttributeValue=function(){return this.matchJSX("{")?this.parseJSXExpressionAttribute():this.matchJSX("<")?this.parseJSXElement():this.parseJSXStringLiteralAttribute()},t.prototype.parseJSXNameValueAttribute=function(){var e=this.createJSXNode(),t=this.parseJSXAttributeName(),i=null;return this.matchJSX("=")&&(this.expectJSX("="),i=this.parseJSXAttributeValue()),this.finalize(e,new a.JSXAttribute(t,i))},t.prototype.parseJSXSpreadAttribute=function(){var e=this.createJSXNode();this.expectJSX("{"),this.expectJSX("..."),this.finishJSX();var t=this.parseAssignmentExpression();return this.reenterJSX(),this.finalize(e,new a.JSXSpreadAttribute(t))},t.prototype.parseJSXAttributes=function(){for(var e=[];!this.matchJSX("/")&&!this.matchJSX(">");){var t=this.matchJSX("{")?this.parseJSXSpreadAttribute():this.parseJSXNameValueAttribute();e.push(t)}return e},t.prototype.parseJSXOpeningElement=function(){var e=this.createJSXNode();this.expectJSX("<");var t=this.parseJSXElementName(),i=this.parseJSXAttributes(),s=this.matchJSX("/");return s&&this.expectJSX("/"),this.expectJSX(">"),this.finalize(e,new a.JSXOpeningElement(t,s,i))},t.prototype.parseJSXBoundaryElement=function(){var e=this.createJSXNode();if(this.expectJSX("<"),this.matchJSX("/")){this.expectJSX("/");var t=this.parseJSXElementName();return this.expectJSX(">"),this.finalize(e,new a.JSXClosingElement(t))}var i=this.parseJSXElementName(),s=this.parseJSXAttributes(),r=this.matchJSX("/");return r&&this.expectJSX("/"),this.expectJSX(">"),this.finalize(e,new a.JSXOpeningElement(i,r,s))},t.prototype.parseJSXEmptyExpression=function(){var e=this.createJSXChildNode();return this.collectComments(),this.lastMarker.index=this.scanner.index,this.lastMarker.line=this.scanner.lineNumber,this.lastMarker.column=this.scanner.index-this.scanner.lineStart,this.finalize(e,new a.JSXEmptyExpression)},t.prototype.parseJSXExpressionContainer=function(){var e,t=this.createJSXNode();return this.expectJSX("{"),this.matchJSX("}")?(e=this.parseJSXEmptyExpression(),this.expectJSX("}")):(this.finishJSX(),e=this.parseAssignmentExpression(),this.reenterJSX()),this.finalize(t,new a.JSXExpressionContainer(e))},t.prototype.parseJSXChildren=function(){for(var e=[];!this.scanner.eof();){var t=this.createJSXChildNode(),i=this.nextJSXText();if(i.start0))break;n=this.finalize(e.node,new a.JSXElement(e.opening,e.children,e.closing)),(e=t[t.length-1]).children.push(n),t.pop()}}return e},t.prototype.parseJSXElement=function(){var e=this.createJSXNode(),t=this.parseJSXOpeningElement(),i=[],s=null;if(!t.selfClosing){var r=this.parseComplexJSXElement({node:e,opening:t,closing:s,children:i});i=r.children,s=r.closing}return this.finalize(e,new a.JSXElement(t,i,s))},t.prototype.parseJSXRoot=function(){this.config.tokens&&this.tokens.pop(),this.startJSX();var e=this.parseJSXElement();return this.finishJSX(),e},t.prototype.isStartOfExpression=function(){return e.prototype.isStartOfExpression.call(this)||this.match("<")},t}(l.Parser);t.JSXParser=d},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i={NonAsciiIdentifierStart:/[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D]/,NonAsciiIdentifierPart:/[\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B4\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/};t.Character={fromCodePoint:function(e){return e<65536?String.fromCharCode(e):String.fromCharCode(55296+(e-65536>>10))+String.fromCharCode(56320+(e-65536&1023))},isWhiteSpace:function(e){return 32===e||9===e||11===e||12===e||160===e||e>=5760&&[5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8239,8287,12288,65279].indexOf(e)>=0},isLineTerminator:function(e){return 10===e||13===e||8232===e||8233===e},isIdentifierStart:function(e){return 36===e||95===e||e>=65&&e<=90||e>=97&&e<=122||92===e||e>=128&&i.NonAsciiIdentifierStart.test(t.Character.fromCodePoint(e))},isIdentifierPart:function(e){return 36===e||95===e||e>=65&&e<=90||e>=97&&e<=122||e>=48&&e<=57||92===e||e>=128&&i.NonAsciiIdentifierPart.test(t.Character.fromCodePoint(e))},isDecimalDigit:function(e){return e>=48&&e<=57},isHexDigit:function(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102},isOctalDigit:function(e){return e>=48&&e<=55}}},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var s=i(6);t.JSXClosingElement=function(e){this.type=s.JSXSyntax.JSXClosingElement,this.name=e};t.JSXElement=function(e,t,i){this.type=s.JSXSyntax.JSXElement,this.openingElement=e,this.children=t,this.closingElement=i};t.JSXEmptyExpression=function(){this.type=s.JSXSyntax.JSXEmptyExpression};t.JSXExpressionContainer=function(e){this.type=s.JSXSyntax.JSXExpressionContainer,this.expression=e};t.JSXIdentifier=function(e){this.type=s.JSXSyntax.JSXIdentifier,this.name=e};t.JSXMemberExpression=function(e,t){this.type=s.JSXSyntax.JSXMemberExpression,this.object=e,this.property=t};t.JSXAttribute=function(e,t){this.type=s.JSXSyntax.JSXAttribute,this.name=e,this.value=t};t.JSXNamespacedName=function(e,t){this.type=s.JSXSyntax.JSXNamespacedName,this.namespace=e,this.name=t};t.JSXOpeningElement=function(e,t,i){this.type=s.JSXSyntax.JSXOpeningElement,this.name=e,this.selfClosing=t,this.attributes=i};t.JSXSpreadAttribute=function(e){this.type=s.JSXSyntax.JSXSpreadAttribute,this.argument=e};t.JSXText=function(e,t){this.type=s.JSXSyntax.JSXText,this.value=e,this.raw=t}},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.JSXSyntax={JSXAttribute:"JSXAttribute",JSXClosingElement:"JSXClosingElement",JSXElement:"JSXElement",JSXEmptyExpression:"JSXEmptyExpression",JSXExpressionContainer:"JSXExpressionContainer",JSXIdentifier:"JSXIdentifier",JSXMemberExpression:"JSXMemberExpression",JSXNamespacedName:"JSXNamespacedName",JSXOpeningElement:"JSXOpeningElement",JSXSpreadAttribute:"JSXSpreadAttribute",JSXText:"JSXText"}},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var s=i(2);t.ArrayExpression=function(e){this.type=s.Syntax.ArrayExpression,this.elements=e};t.ArrayPattern=function(e){this.type=s.Syntax.ArrayPattern,this.elements=e};t.ArrowFunctionExpression=function(e,t,i){this.type=s.Syntax.ArrowFunctionExpression,this.id=null,this.params=e,this.body=t,this.generator=!1,this.expression=i,this.async=!1};t.AssignmentExpression=function(e,t,i){this.type=s.Syntax.AssignmentExpression,this.operator=e,this.left=t,this.right=i};t.AssignmentPattern=function(e,t){this.type=s.Syntax.AssignmentPattern,this.left=e,this.right=t};t.AsyncArrowFunctionExpression=function(e,t,i){this.type=s.Syntax.ArrowFunctionExpression,this.id=null,this.params=e,this.body=t,this.generator=!1,this.expression=i,this.async=!0};t.AsyncFunctionDeclaration=function(e,t,i){this.type=s.Syntax.FunctionDeclaration,this.id=e,this.params=t,this.body=i,this.generator=!1,this.expression=!1,this.async=!0};t.AsyncFunctionExpression=function(e,t,i){this.type=s.Syntax.FunctionExpression,this.id=e,this.params=t,this.body=i,this.generator=!1,this.expression=!1,this.async=!0};t.AwaitExpression=function(e){this.type=s.Syntax.AwaitExpression,this.argument=e};t.BinaryExpression=function(e,t,i){var r="||"===e||"&&"===e;this.type=r?s.Syntax.LogicalExpression:s.Syntax.BinaryExpression,this.operator=e,this.left=t,this.right=i};t.BlockStatement=function(e){this.type=s.Syntax.BlockStatement,this.body=e};t.BreakStatement=function(e){this.type=s.Syntax.BreakStatement,this.label=e};t.CallExpression=function(e,t){this.type=s.Syntax.CallExpression,this.callee=e,this.arguments=t};t.CatchClause=function(e,t){this.type=s.Syntax.CatchClause,this.param=e,this.body=t};t.ClassBody=function(e){this.type=s.Syntax.ClassBody,this.body=e};t.ClassDeclaration=function(e,t,i){this.type=s.Syntax.ClassDeclaration,this.id=e,this.superClass=t,this.body=i};t.ClassExpression=function(e,t,i){this.type=s.Syntax.ClassExpression,this.id=e,this.superClass=t,this.body=i};t.ComputedMemberExpression=function(e,t){this.type=s.Syntax.MemberExpression,this.computed=!0,this.object=e,this.property=t};t.ConditionalExpression=function(e,t,i){this.type=s.Syntax.ConditionalExpression,this.test=e,this.consequent=t,this.alternate=i};t.ContinueStatement=function(e){this.type=s.Syntax.ContinueStatement,this.label=e};t.DebuggerStatement=function(){this.type=s.Syntax.DebuggerStatement};t.Directive=function(e,t){this.type=s.Syntax.ExpressionStatement,this.expression=e,this.directive=t};t.DoWhileStatement=function(e,t){this.type=s.Syntax.DoWhileStatement,this.body=e,this.test=t};t.EmptyStatement=function(){this.type=s.Syntax.EmptyStatement};t.ExportAllDeclaration=function(e){this.type=s.Syntax.ExportAllDeclaration,this.source=e};t.ExportDefaultDeclaration=function(e){this.type=s.Syntax.ExportDefaultDeclaration,this.declaration=e};t.ExportNamedDeclaration=function(e,t,i){this.type=s.Syntax.ExportNamedDeclaration,this.declaration=e,this.specifiers=t,this.source=i};t.ExportSpecifier=function(e,t){this.type=s.Syntax.ExportSpecifier,this.exported=t,this.local=e};t.ExpressionStatement=function(e){this.type=s.Syntax.ExpressionStatement,this.expression=e};t.ForInStatement=function(e,t,i){this.type=s.Syntax.ForInStatement,this.left=e,this.right=t,this.body=i,this.each=!1};t.ForOfStatement=function(e,t,i){this.type=s.Syntax.ForOfStatement,this.left=e,this.right=t,this.body=i};t.ForStatement=function(e,t,i,r){this.type=s.Syntax.ForStatement,this.init=e,this.test=t,this.update=i,this.body=r};t.FunctionDeclaration=function(e,t,i,r){this.type=s.Syntax.FunctionDeclaration,this.id=e,this.params=t,this.body=i,this.generator=r,this.expression=!1,this.async=!1};t.FunctionExpression=function(e,t,i,r){this.type=s.Syntax.FunctionExpression,this.id=e,this.params=t,this.body=i,this.generator=r,this.expression=!1,this.async=!1};t.Identifier=function(e){this.type=s.Syntax.Identifier,this.name=e};t.IfStatement=function(e,t,i){this.type=s.Syntax.IfStatement,this.test=e,this.consequent=t,this.alternate=i};t.ImportDeclaration=function(e,t){this.type=s.Syntax.ImportDeclaration,this.specifiers=e,this.source=t};t.ImportDefaultSpecifier=function(e){this.type=s.Syntax.ImportDefaultSpecifier,this.local=e};t.ImportNamespaceSpecifier=function(e){this.type=s.Syntax.ImportNamespaceSpecifier,this.local=e};t.ImportSpecifier=function(e,t){this.type=s.Syntax.ImportSpecifier,this.local=e,this.imported=t};t.LabeledStatement=function(e,t){this.type=s.Syntax.LabeledStatement,this.label=e,this.body=t};t.Literal=function(e,t){this.type=s.Syntax.Literal,this.value=e,this.raw=t};t.MetaProperty=function(e,t){this.type=s.Syntax.MetaProperty,this.meta=e,this.property=t};t.MethodDefinition=function(e,t,i,r,n){this.type=s.Syntax.MethodDefinition,this.key=e,this.computed=t,this.value=i,this.kind=r,this.static=n};t.Module=function(e){this.type=s.Syntax.Program,this.body=e,this.sourceType="module"};t.NewExpression=function(e,t){this.type=s.Syntax.NewExpression,this.callee=e,this.arguments=t};t.ObjectExpression=function(e){this.type=s.Syntax.ObjectExpression,this.properties=e};t.ObjectPattern=function(e){this.type=s.Syntax.ObjectPattern,this.properties=e};t.Property=function(e,t,i,r,n,a){this.type=s.Syntax.Property,this.key=t,this.computed=i,this.value=r,this.kind=e,this.method=n,this.shorthand=a};t.RegexLiteral=function(e,t,i,r){this.type=s.Syntax.Literal,this.value=e,this.raw=t,this.regex={pattern:i,flags:r}};t.RestElement=function(e){this.type=s.Syntax.RestElement,this.argument=e};t.ReturnStatement=function(e){this.type=s.Syntax.ReturnStatement,this.argument=e};t.Script=function(e){this.type=s.Syntax.Program,this.body=e,this.sourceType="script"};t.SequenceExpression=function(e){this.type=s.Syntax.SequenceExpression,this.expressions=e};t.SpreadElement=function(e){this.type=s.Syntax.SpreadElement,this.argument=e};t.StaticMemberExpression=function(e,t){this.type=s.Syntax.MemberExpression,this.computed=!1,this.object=e,this.property=t};t.Super=function(){this.type=s.Syntax.Super};t.SwitchCase=function(e,t){this.type=s.Syntax.SwitchCase,this.test=e,this.consequent=t};t.SwitchStatement=function(e,t){this.type=s.Syntax.SwitchStatement,this.discriminant=e,this.cases=t};t.TaggedTemplateExpression=function(e,t){this.type=s.Syntax.TaggedTemplateExpression,this.tag=e,this.quasi=t};t.TemplateElement=function(e,t){this.type=s.Syntax.TemplateElement,this.value=e,this.tail=t};t.TemplateLiteral=function(e,t){this.type=s.Syntax.TemplateLiteral,this.quasis=e,this.expressions=t};t.ThisExpression=function(){this.type=s.Syntax.ThisExpression};t.ThrowStatement=function(e){this.type=s.Syntax.ThrowStatement,this.argument=e};t.TryStatement=function(e,t,i){this.type=s.Syntax.TryStatement,this.block=e,this.handler=t,this.finalizer=i};t.UnaryExpression=function(e,t){this.type=s.Syntax.UnaryExpression,this.operator=e,this.argument=t,this.prefix=!0};t.UpdateExpression=function(e,t,i){this.type=s.Syntax.UpdateExpression,this.operator=e,this.argument=t,this.prefix=i};t.VariableDeclaration=function(e,t){this.type=s.Syntax.VariableDeclaration,this.declarations=e,this.kind=t};t.VariableDeclarator=function(e,t){this.type=s.Syntax.VariableDeclarator,this.id=e,this.init=t};t.WhileStatement=function(e,t){this.type=s.Syntax.WhileStatement,this.test=e,this.body=t};t.WithStatement=function(e,t){this.type=s.Syntax.WithStatement,this.object=e,this.body=t};t.YieldExpression=function(e,t){this.type=s.Syntax.YieldExpression,this.argument=e,this.delegate=t}},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var s=i(9),r=i(10),n=i(11),a=i(7),o=i(12),c=i(2),l=i(13),p="ArrowParameterPlaceHolder",A=function(){function e(e,t,i){void 0===t&&(t={}),this.config={range:"boolean"==typeof t.range&&t.range,loc:"boolean"==typeof t.loc&&t.loc,source:null,tokens:"boolean"==typeof t.tokens&&t.tokens,comment:"boolean"==typeof t.comment&&t.comment,tolerant:"boolean"==typeof t.tolerant&&t.tolerant},this.config.loc&&t.source&&null!==t.source&&(this.config.source=String(t.source)),this.delegate=i,this.errorHandler=new r.ErrorHandler,this.errorHandler.tolerant=this.config.tolerant,this.scanner=new o.Scanner(e,this.errorHandler),this.scanner.trackComment=this.config.comment,this.operatorPrecedence={")":0,";":0,",":0,"=":0,"]":0,"||":1,"&&":2,"|":3,"^":4,"&":5,"==":6,"!=":6,"===":6,"!==":6,"<":7,">":7,"<=":7,">=":7,"<<":8,">>":8,">>>":8,"+":9,"-":9,"*":11,"/":11,"%":11},this.lookahead={type:2,value:"",lineNumber:this.scanner.lineNumber,lineStart:0,start:0,end:0},this.hasLineTerminator=!1,this.context={isModule:!1,await:!1,allowIn:!0,allowStrictDirective:!0,allowYield:!0,firstCoverInitializedNameError:null,isAssignmentTarget:!1,isBindingElement:!1,inFunctionBody:!1,inIteration:!1,inSwitch:!1,labelSet:{},strict:!1},this.tokens=[],this.startMarker={index:0,line:this.scanner.lineNumber,column:0},this.lastMarker={index:0,line:this.scanner.lineNumber,column:0},this.nextToken(),this.lastMarker={index:this.scanner.index,line:this.scanner.lineNumber,column:this.scanner.index-this.scanner.lineStart}}return e.prototype.throwError=function(e){for(var t=[],i=1;i0&&this.delegate)for(var t=0;t>="===e||">>>="===e||"&="===e||"^="===e||"|="===e},e.prototype.isolateCoverGrammar=function(e){var t=this.context.isBindingElement,i=this.context.isAssignmentTarget,s=this.context.firstCoverInitializedNameError;this.context.isBindingElement=!0,this.context.isAssignmentTarget=!0,this.context.firstCoverInitializedNameError=null;var r=e.call(this);return null!==this.context.firstCoverInitializedNameError&&this.throwUnexpectedToken(this.context.firstCoverInitializedNameError),this.context.isBindingElement=t,this.context.isAssignmentTarget=i,this.context.firstCoverInitializedNameError=s,r},e.prototype.inheritCoverGrammar=function(e){var t=this.context.isBindingElement,i=this.context.isAssignmentTarget,s=this.context.firstCoverInitializedNameError;this.context.isBindingElement=!0,this.context.isAssignmentTarget=!0,this.context.firstCoverInitializedNameError=null;var r=e.call(this);return this.context.isBindingElement=this.context.isBindingElement&&t,this.context.isAssignmentTarget=this.context.isAssignmentTarget&&i,this.context.firstCoverInitializedNameError=s||this.context.firstCoverInitializedNameError,r},e.prototype.consumeSemicolon=function(){this.match(";")?this.nextToken():this.hasLineTerminator||(2===this.lookahead.type||this.match("}")||this.throwUnexpectedToken(this.lookahead),this.lastMarker.index=this.startMarker.index,this.lastMarker.line=this.startMarker.line,this.lastMarker.column=this.startMarker.column)},e.prototype.parsePrimaryExpression=function(){var e,t,i,s=this.createNode();switch(this.lookahead.type){case 3:(this.context.isModule||this.context.await)&&"await"===this.lookahead.value&&this.tolerateUnexpectedToken(this.lookahead),e=this.matchAsyncFunction()?this.parseFunctionExpression():this.finalize(s,new a.Identifier(this.nextToken().value));break;case 6:case 8:this.context.strict&&this.lookahead.octal&&this.tolerateUnexpectedToken(this.lookahead,n.Messages.StrictOctalLiteral),this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1,t=this.nextToken(),i=this.getTokenRaw(t),e=this.finalize(s,new a.Literal(t.value,i));break;case 1:this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1,t=this.nextToken(),i=this.getTokenRaw(t),e=this.finalize(s,new a.Literal("true"===t.value,i));break;case 5:this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1,t=this.nextToken(),i=this.getTokenRaw(t),e=this.finalize(s,new a.Literal(null,i));break;case 10:e=this.parseTemplateLiteral();break;case 7:switch(this.lookahead.value){case"(":this.context.isBindingElement=!1,e=this.inheritCoverGrammar(this.parseGroupExpression);break;case"[":e=this.inheritCoverGrammar(this.parseArrayInitializer);break;case"{":e=this.inheritCoverGrammar(this.parseObjectInitializer);break;case"/":case"/=":this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1,this.scanner.index=this.startMarker.index,t=this.nextRegexToken(),i=this.getTokenRaw(t),e=this.finalize(s,new a.RegexLiteral(t.regex,i,t.pattern,t.flags));break;default:e=this.throwUnexpectedToken(this.nextToken())}break;case 4:!this.context.strict&&this.context.allowYield&&this.matchKeyword("yield")?e=this.parseIdentifierName():!this.context.strict&&this.matchKeyword("let")?e=this.finalize(s,new a.Identifier(this.nextToken().value)):(this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1,this.matchKeyword("function")?e=this.parseFunctionExpression():this.matchKeyword("this")?(this.nextToken(),e=this.finalize(s,new a.ThisExpression)):e=this.matchKeyword("class")?this.parseClassExpression():this.throwUnexpectedToken(this.nextToken()));break;default:e=this.throwUnexpectedToken(this.nextToken())}return e},e.prototype.parseSpreadElement=function(){var e=this.createNode();this.expect("...");var t=this.inheritCoverGrammar(this.parseAssignmentExpression);return this.finalize(e,new a.SpreadElement(t))},e.prototype.parseArrayInitializer=function(){var e=this.createNode(),t=[];for(this.expect("[");!this.match("]");)if(this.match(","))this.nextToken(),t.push(null);else if(this.match("...")){var i=this.parseSpreadElement();this.match("]")||(this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1,this.expect(",")),t.push(i)}else t.push(this.inheritCoverGrammar(this.parseAssignmentExpression)),this.match("]")||this.expect(",");return this.expect("]"),this.finalize(e,new a.ArrayExpression(t))},e.prototype.parsePropertyMethod=function(e){this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1;var t=this.context.strict,i=this.context.allowStrictDirective;this.context.allowStrictDirective=e.simple;var s=this.isolateCoverGrammar(this.parseFunctionSourceElements);return this.context.strict&&e.firstRestricted&&this.tolerateUnexpectedToken(e.firstRestricted,e.message),this.context.strict&&e.stricted&&this.tolerateUnexpectedToken(e.stricted,e.message),this.context.strict=t,this.context.allowStrictDirective=i,s},e.prototype.parsePropertyMethodFunction=function(){var e=this.createNode(),t=this.context.allowYield;this.context.allowYield=!0;var i=this.parseFormalParameters(),s=this.parsePropertyMethod(i);return this.context.allowYield=t,this.finalize(e,new a.FunctionExpression(null,i.params,s,!1))},e.prototype.parsePropertyMethodAsyncFunction=function(){var e=this.createNode(),t=this.context.allowYield,i=this.context.await;this.context.allowYield=!1,this.context.await=!0;var s=this.parseFormalParameters(),r=this.parsePropertyMethod(s);return this.context.allowYield=t,this.context.await=i,this.finalize(e,new a.AsyncFunctionExpression(null,s.params,r))},e.prototype.parseObjectPropertyKey=function(){var e,t=this.createNode(),i=this.nextToken();switch(i.type){case 8:case 6:this.context.strict&&i.octal&&this.tolerateUnexpectedToken(i,n.Messages.StrictOctalLiteral);var s=this.getTokenRaw(i);e=this.finalize(t,new a.Literal(i.value,s));break;case 3:case 1:case 5:case 4:e=this.finalize(t,new a.Identifier(i.value));break;case 7:"["===i.value?(e=this.isolateCoverGrammar(this.parseAssignmentExpression),this.expect("]")):e=this.throwUnexpectedToken(i);break;default:e=this.throwUnexpectedToken(i)}return e},e.prototype.isPropertyKey=function(e,t){return e.type===c.Syntax.Identifier&&e.name===t||e.type===c.Syntax.Literal&&e.value===t},e.prototype.parseObjectProperty=function(e){var t,i=this.createNode(),s=this.lookahead,r=null,o=null,c=!1,l=!1,p=!1,A=!1;if(3===s.type){var u=s.value;this.nextToken(),c=this.match("["),r=(A=!(this.hasLineTerminator||"async"!==u||this.match(":")||this.match("(")||this.match("*")||this.match(",")))?this.parseObjectPropertyKey():this.finalize(i,new a.Identifier(u))}else this.match("*")?this.nextToken():(c=this.match("["),r=this.parseObjectPropertyKey());var d=this.qualifiedPropertyName(this.lookahead);if(3===s.type&&!A&&"get"===s.value&&d)t="get",c=this.match("["),r=this.parseObjectPropertyKey(),this.context.allowYield=!1,o=this.parseGetterMethod();else if(3===s.type&&!A&&"set"===s.value&&d)t="set",c=this.match("["),r=this.parseObjectPropertyKey(),o=this.parseSetterMethod();else if(7===s.type&&"*"===s.value&&d)t="init",c=this.match("["),r=this.parseObjectPropertyKey(),o=this.parseGeneratorMethod(),l=!0;else if(r||this.throwUnexpectedToken(this.lookahead),t="init",this.match(":")&&!A)!c&&this.isPropertyKey(r,"__proto__")&&(e.value&&this.tolerateError(n.Messages.DuplicateProtoProperty),e.value=!0),this.nextToken(),o=this.inheritCoverGrammar(this.parseAssignmentExpression);else if(this.match("("))o=A?this.parsePropertyMethodAsyncFunction():this.parsePropertyMethodFunction(),l=!0;else if(3===s.type)if(u=this.finalize(i,new a.Identifier(s.value)),this.match("=")){this.context.firstCoverInitializedNameError=this.lookahead,this.nextToken(),p=!0;var h=this.isolateCoverGrammar(this.parseAssignmentExpression);o=this.finalize(i,new a.AssignmentPattern(u,h))}else p=!0,o=u;else this.throwUnexpectedToken(this.nextToken());return this.finalize(i,new a.Property(t,r,c,o,l,p))},e.prototype.parseObjectInitializer=function(){var e=this.createNode();this.expect("{");for(var t=[],i={value:!1};!this.match("}");)t.push(this.parseObjectProperty(i)),this.match("}")||this.expectCommaSeparator();return this.expect("}"),this.finalize(e,new a.ObjectExpression(t))},e.prototype.parseTemplateHead=function(){s.assert(this.lookahead.head,"Template literal must start with a template head");var e=this.createNode(),t=this.nextToken(),i=t.value,r=t.cooked;return this.finalize(e,new a.TemplateElement({raw:i,cooked:r},t.tail))},e.prototype.parseTemplateElement=function(){10!==this.lookahead.type&&this.throwUnexpectedToken();var e=this.createNode(),t=this.nextToken(),i=t.value,s=t.cooked;return this.finalize(e,new a.TemplateElement({raw:i,cooked:s},t.tail))},e.prototype.parseTemplateLiteral=function(){var e=this.createNode(),t=[],i=[],s=this.parseTemplateHead();for(i.push(s);!s.tail;)t.push(this.parseExpression()),s=this.parseTemplateElement(),i.push(s);return this.finalize(e,new a.TemplateLiteral(i,t))},e.prototype.reinterpretExpressionAsPattern=function(e){switch(e.type){case c.Syntax.Identifier:case c.Syntax.MemberExpression:case c.Syntax.RestElement:case c.Syntax.AssignmentPattern:break;case c.Syntax.SpreadElement:e.type=c.Syntax.RestElement,this.reinterpretExpressionAsPattern(e.argument);break;case c.Syntax.ArrayExpression:e.type=c.Syntax.ArrayPattern;for(var t=0;t")||this.expect("=>"),e={type:p,params:[],async:!1};else{var t=this.lookahead,i=[];if(this.match("..."))e=this.parseRestElement(i),this.expect(")"),this.match("=>")||this.expect("=>"),e={type:p,params:[e],async:!1};else{var s=!1;if(this.context.isBindingElement=!0,e=this.inheritCoverGrammar(this.parseAssignmentExpression),this.match(",")){var r=[];for(this.context.isAssignmentTarget=!1,r.push(e);2!==this.lookahead.type&&this.match(",");){if(this.nextToken(),this.match(")")){this.nextToken();for(var n=0;n")||this.expect("=>"),this.context.isBindingElement=!1,n=0;n")&&(e.type===c.Syntax.Identifier&&"yield"===e.name&&(s=!0,e={type:p,params:[e],async:!1}),!s)){if(this.context.isBindingElement||this.throwUnexpectedToken(this.lookahead),e.type===c.Syntax.SequenceExpression)for(n=0;n")){for(var c=0;c0){this.nextToken(),this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1;for(var r=[e,this.lookahead],n=t,o=this.isolateCoverGrammar(this.parseExponentiationExpression),c=[n,i.value,o],l=[s];!((s=this.binaryPrecedence(this.lookahead))<=0);){for(;c.length>2&&s<=l[l.length-1];){o=c.pop();var p=c.pop();l.pop(),n=c.pop(),r.pop();var A=this.startNode(r[r.length-1]);c.push(this.finalize(A,new a.BinaryExpression(p,n,o)))}c.push(this.nextToken().value),l.push(s),r.push(this.lookahead),c.push(this.isolateCoverGrammar(this.parseExponentiationExpression))}var u=c.length-1;t=c[u];for(var d=r.pop();u>1;){var h=r.pop(),m=d&&d.lineStart;A=this.startNode(h,m),p=c[u-1],t=this.finalize(A,new a.BinaryExpression(p,c[u-2],t)),u-=2,d=h}}return t},e.prototype.parseConditionalExpression=function(){var e=this.lookahead,t=this.inheritCoverGrammar(this.parseBinaryExpression);if(this.match("?")){this.nextToken();var i=this.context.allowIn;this.context.allowIn=!0;var s=this.isolateCoverGrammar(this.parseAssignmentExpression);this.context.allowIn=i,this.expect(":");var r=this.isolateCoverGrammar(this.parseAssignmentExpression);t=this.finalize(this.startNode(e),new a.ConditionalExpression(t,s,r)),this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1}return t},e.prototype.checkPatternParam=function(e,t){switch(t.type){case c.Syntax.Identifier:this.validateParam(e,t,t.name);break;case c.Syntax.RestElement:this.checkPatternParam(e,t.argument);break;case c.Syntax.AssignmentPattern:this.checkPatternParam(e,t.left);break;case c.Syntax.ArrayPattern:for(var i=0;i")){this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1;var r=e.async,o=this.reinterpretAsCoverFormalsList(e);if(o){this.hasLineTerminator&&this.tolerateUnexpectedToken(this.lookahead),this.context.firstCoverInitializedNameError=null;var l=this.context.strict,A=this.context.allowStrictDirective;this.context.allowStrictDirective=o.simple;var u=this.context.allowYield,d=this.context.await;this.context.allowYield=!0,this.context.await=r;var h=this.startNode(t);this.expect("=>");var m=void 0;if(this.match("{")){var g=this.context.allowIn;this.context.allowIn=!0,m=this.parseFunctionSourceElements(),this.context.allowIn=g}else m=this.isolateCoverGrammar(this.parseAssignmentExpression);var f=m.type!==c.Syntax.BlockStatement;this.context.strict&&o.firstRestricted&&this.throwUnexpectedToken(o.firstRestricted,o.message),this.context.strict&&o.stricted&&this.tolerateUnexpectedToken(o.stricted,o.message),e=r?this.finalize(h,new a.AsyncArrowFunctionExpression(o.params,m,f)):this.finalize(h,new a.ArrowFunctionExpression(o.params,m,f)),this.context.strict=l,this.context.allowStrictDirective=A,this.context.allowYield=u,this.context.await=d}}else if(this.matchAssign()){if(this.context.isAssignmentTarget||this.tolerateError(n.Messages.InvalidLHSInAssignment),this.context.strict&&e.type===c.Syntax.Identifier){var E=e;this.scanner.isRestrictedWord(E.name)&&this.tolerateUnexpectedToken(i,n.Messages.StrictLHSAssignment),this.scanner.isStrictModeReservedWord(E.name)&&this.tolerateUnexpectedToken(i,n.Messages.StrictReservedWord)}this.match("=")?this.reinterpretExpressionAsPattern(e):(this.context.isAssignmentTarget=!1,this.context.isBindingElement=!1);var C=(i=this.nextToken()).value,y=this.isolateCoverGrammar(this.parseAssignmentExpression);e=this.finalize(this.startNode(t),new a.AssignmentExpression(C,e,y)),this.context.firstCoverInitializedNameError=null}}return e},e.prototype.parseExpression=function(){var e=this.lookahead,t=this.isolateCoverGrammar(this.parseAssignmentExpression);if(this.match(",")){var i=[];for(i.push(t);2!==this.lookahead.type&&this.match(",");)this.nextToken(),i.push(this.isolateCoverGrammar(this.parseAssignmentExpression));t=this.finalize(this.startNode(e),new a.SequenceExpression(i))}return t},e.prototype.parseStatementListItem=function(){var e;if(this.context.isAssignmentTarget=!0,this.context.isBindingElement=!0,4===this.lookahead.type)switch(this.lookahead.value){case"export":this.context.isModule||this.tolerateUnexpectedToken(this.lookahead,n.Messages.IllegalExportDeclaration),e=this.parseExportDeclaration();break;case"import":this.context.isModule||this.tolerateUnexpectedToken(this.lookahead,n.Messages.IllegalImportDeclaration),e=this.parseImportDeclaration();break;case"const":e=this.parseLexicalDeclaration({inFor:!1});break;case"function":e=this.parseFunctionDeclaration();break;case"class":e=this.parseClassDeclaration();break;case"let":e=this.isLexicalDeclaration()?this.parseLexicalDeclaration({inFor:!1}):this.parseStatement();break;default:e=this.parseStatement()}else e=this.parseStatement();return e},e.prototype.parseBlock=function(){var e=this.createNode();this.expect("{");for(var t=[];!this.match("}");)t.push(this.parseStatementListItem());return this.expect("}"),this.finalize(e,new a.BlockStatement(t))},e.prototype.parseLexicalBinding=function(e,t){var i=this.createNode(),s=this.parsePattern([],e);this.context.strict&&s.type===c.Syntax.Identifier&&this.scanner.isRestrictedWord(s.name)&&this.tolerateError(n.Messages.StrictVarName);var r=null;return"const"===e?this.matchKeyword("in")||this.matchContextualKeyword("of")||(this.match("=")?(this.nextToken(),r=this.isolateCoverGrammar(this.parseAssignmentExpression)):this.throwError(n.Messages.DeclarationMissingInitializer,"const")):(!t.inFor&&s.type!==c.Syntax.Identifier||this.match("="))&&(this.expect("="),r=this.isolateCoverGrammar(this.parseAssignmentExpression)),this.finalize(i,new a.VariableDeclarator(s,r))},e.prototype.parseBindingList=function(e,t){for(var i=[this.parseLexicalBinding(e,t)];this.match(",");)this.nextToken(),i.push(this.parseLexicalBinding(e,t));return i},e.prototype.isLexicalDeclaration=function(){var e=this.scanner.saveState();this.scanner.scanComments();var t=this.scanner.lex();return this.scanner.restoreState(e),3===t.type||7===t.type&&"["===t.value||7===t.type&&"{"===t.value||4===t.type&&"let"===t.value||4===t.type&&"yield"===t.value},e.prototype.parseLexicalDeclaration=function(e){var t=this.createNode(),i=this.nextToken().value;s.assert("let"===i||"const"===i,"Lexical declaration must be either let or const");var r=this.parseBindingList(i,e);return this.consumeSemicolon(),this.finalize(t,new a.VariableDeclaration(r,i))},e.prototype.parseBindingRestElement=function(e,t){var i=this.createNode();this.expect("...");var s=this.parsePattern(e,t);return this.finalize(i,new a.RestElement(s))},e.prototype.parseArrayPattern=function(e,t){var i=this.createNode();this.expect("[");for(var s=[];!this.match("]");)if(this.match(","))this.nextToken(),s.push(null);else{if(this.match("...")){s.push(this.parseBindingRestElement(e,t));break}s.push(this.parsePatternWithDefault(e,t)),this.match("]")||this.expect(",")}return this.expect("]"),this.finalize(i,new a.ArrayPattern(s))},e.prototype.parsePropertyPattern=function(e,t){var i,s,r=this.createNode(),n=!1,o=!1;if(3===this.lookahead.type){var c=this.lookahead;i=this.parseVariableIdentifier();var l=this.finalize(r,new a.Identifier(c.value));if(this.match("=")){e.push(c),o=!0,this.nextToken();var p=this.parseAssignmentExpression();s=this.finalize(this.startNode(c),new a.AssignmentPattern(l,p))}else this.match(":")?(this.expect(":"),s=this.parsePatternWithDefault(e,t)):(e.push(c),o=!0,s=l)}else n=this.match("["),i=this.parseObjectPropertyKey(),this.expect(":"),s=this.parsePatternWithDefault(e,t);return this.finalize(r,new a.Property("init",i,n,s,!1,o))},e.prototype.parseObjectPattern=function(e,t){var i=this.createNode(),s=[];for(this.expect("{");!this.match("}");)s.push(this.parsePropertyPattern(e,t)),this.match("}")||this.expect(",");return this.expect("}"),this.finalize(i,new a.ObjectPattern(s))},e.prototype.parsePattern=function(e,t){var i;return this.match("[")?i=this.parseArrayPattern(e,t):this.match("{")?i=this.parseObjectPattern(e,t):(!this.matchKeyword("let")||"const"!==t&&"let"!==t||this.tolerateUnexpectedToken(this.lookahead,n.Messages.LetInLexicalBinding),e.push(this.lookahead),i=this.parseVariableIdentifier(t)),i},e.prototype.parsePatternWithDefault=function(e,t){var i=this.lookahead,s=this.parsePattern(e,t);if(this.match("=")){this.nextToken();var r=this.context.allowYield;this.context.allowYield=!0;var n=this.isolateCoverGrammar(this.parseAssignmentExpression);this.context.allowYield=r,s=this.finalize(this.startNode(i),new a.AssignmentPattern(s,n))}return s},e.prototype.parseVariableIdentifier=function(e){var t=this.createNode(),i=this.nextToken();return 4===i.type&&"yield"===i.value?this.context.strict?this.tolerateUnexpectedToken(i,n.Messages.StrictReservedWord):this.context.allowYield||this.throwUnexpectedToken(i):3!==i.type?this.context.strict&&4===i.type&&this.scanner.isStrictModeReservedWord(i.value)?this.tolerateUnexpectedToken(i,n.Messages.StrictReservedWord):(this.context.strict||"let"!==i.value||"var"!==e)&&this.throwUnexpectedToken(i):(this.context.isModule||this.context.await)&&3===i.type&&"await"===i.value&&this.tolerateUnexpectedToken(i),this.finalize(t,new a.Identifier(i.value))},e.prototype.parseVariableDeclaration=function(e){var t=this.createNode(),i=this.parsePattern([],"var");this.context.strict&&i.type===c.Syntax.Identifier&&this.scanner.isRestrictedWord(i.name)&&this.tolerateError(n.Messages.StrictVarName);var s=null;return this.match("=")?(this.nextToken(),s=this.isolateCoverGrammar(this.parseAssignmentExpression)):i.type===c.Syntax.Identifier||e.inFor||this.expect("="),this.finalize(t,new a.VariableDeclarator(i,s))},e.prototype.parseVariableDeclarationList=function(e){var t={inFor:e.inFor},i=[];for(i.push(this.parseVariableDeclaration(t));this.match(",");)this.nextToken(),i.push(this.parseVariableDeclaration(t));return i},e.prototype.parseVariableStatement=function(){var e=this.createNode();this.expectKeyword("var");var t=this.parseVariableDeclarationList({inFor:!1});return this.consumeSemicolon(),this.finalize(e,new a.VariableDeclaration(t,"var"))},e.prototype.parseEmptyStatement=function(){var e=this.createNode();return this.expect(";"),this.finalize(e,new a.EmptyStatement)},e.prototype.parseExpressionStatement=function(){var e=this.createNode(),t=this.parseExpression();return this.consumeSemicolon(),this.finalize(e,new a.ExpressionStatement(t))},e.prototype.parseIfClause=function(){return this.context.strict&&this.matchKeyword("function")&&this.tolerateError(n.Messages.StrictFunction),this.parseStatement()},e.prototype.parseIfStatement=function(){var e,t=this.createNode(),i=null;this.expectKeyword("if"),this.expect("(");var s=this.parseExpression();return!this.match(")")&&this.config.tolerant?(this.tolerateUnexpectedToken(this.nextToken()),e=this.finalize(this.createNode(),new a.EmptyStatement)):(this.expect(")"),e=this.parseIfClause(),this.matchKeyword("else")&&(this.nextToken(),i=this.parseIfClause())),this.finalize(t,new a.IfStatement(s,e,i))},e.prototype.parseDoWhileStatement=function(){var e=this.createNode();this.expectKeyword("do");var t=this.context.inIteration;this.context.inIteration=!0;var i=this.parseStatement();this.context.inIteration=t,this.expectKeyword("while"),this.expect("(");var s=this.parseExpression();return!this.match(")")&&this.config.tolerant?this.tolerateUnexpectedToken(this.nextToken()):(this.expect(")"),this.match(";")&&this.nextToken()),this.finalize(e,new a.DoWhileStatement(i,s))},e.prototype.parseWhileStatement=function(){var e,t=this.createNode();this.expectKeyword("while"),this.expect("(");var i=this.parseExpression();if(!this.match(")")&&this.config.tolerant)this.tolerateUnexpectedToken(this.nextToken()),e=this.finalize(this.createNode(),new a.EmptyStatement);else{this.expect(")");var s=this.context.inIteration;this.context.inIteration=!0,e=this.parseStatement(),this.context.inIteration=s}return this.finalize(t,new a.WhileStatement(i,e))},e.prototype.parseForStatement=function(){var e,t,i,s=null,r=null,o=null,l=!0,p=this.createNode();if(this.expectKeyword("for"),this.expect("("),this.match(";"))this.nextToken();else if(this.matchKeyword("var")){s=this.createNode(),this.nextToken();var A=this.context.allowIn;this.context.allowIn=!1;var u=this.parseVariableDeclarationList({inFor:!0});if(this.context.allowIn=A,1===u.length&&this.matchKeyword("in")){var d=u[0];d.init&&(d.id.type===c.Syntax.ArrayPattern||d.id.type===c.Syntax.ObjectPattern||this.context.strict)&&this.tolerateError(n.Messages.ForInOfLoopInitializer,"for-in"),s=this.finalize(s,new a.VariableDeclaration(u,"var")),this.nextToken(),e=s,t=this.parseExpression(),s=null}else 1===u.length&&null===u[0].init&&this.matchContextualKeyword("of")?(s=this.finalize(s,new a.VariableDeclaration(u,"var")),this.nextToken(),e=s,t=this.parseAssignmentExpression(),s=null,l=!1):(s=this.finalize(s,new a.VariableDeclaration(u,"var")),this.expect(";"))}else if(this.matchKeyword("const")||this.matchKeyword("let")){s=this.createNode();var h=this.nextToken().value;this.context.strict||"in"!==this.lookahead.value?(A=this.context.allowIn,this.context.allowIn=!1,u=this.parseBindingList(h,{inFor:!0}),this.context.allowIn=A,1===u.length&&null===u[0].init&&this.matchKeyword("in")?(s=this.finalize(s,new a.VariableDeclaration(u,h)),this.nextToken(),e=s,t=this.parseExpression(),s=null):1===u.length&&null===u[0].init&&this.matchContextualKeyword("of")?(s=this.finalize(s,new a.VariableDeclaration(u,h)),this.nextToken(),e=s,t=this.parseAssignmentExpression(),s=null,l=!1):(this.consumeSemicolon(),s=this.finalize(s,new a.VariableDeclaration(u,h)))):(s=this.finalize(s,new a.Identifier(h)),this.nextToken(),e=s,t=this.parseExpression(),s=null)}else{var m=this.lookahead;if(A=this.context.allowIn,this.context.allowIn=!1,s=this.inheritCoverGrammar(this.parseAssignmentExpression),this.context.allowIn=A,this.matchKeyword("in"))this.context.isAssignmentTarget&&s.type!==c.Syntax.AssignmentExpression||this.tolerateError(n.Messages.InvalidLHSInForIn),this.nextToken(),this.reinterpretExpressionAsPattern(s),e=s,t=this.parseExpression(),s=null;else if(this.matchContextualKeyword("of"))this.context.isAssignmentTarget&&s.type!==c.Syntax.AssignmentExpression||this.tolerateError(n.Messages.InvalidLHSInForLoop),this.nextToken(),this.reinterpretExpressionAsPattern(s),e=s,t=this.parseAssignmentExpression(),s=null,l=!1;else{if(this.match(",")){for(var g=[s];this.match(",");)this.nextToken(),g.push(this.isolateCoverGrammar(this.parseAssignmentExpression));s=this.finalize(this.startNode(m),new a.SequenceExpression(g))}this.expect(";")}}if(void 0===e&&(this.match(";")||(r=this.parseExpression()),this.expect(";"),this.match(")")||(o=this.parseExpression())),!this.match(")")&&this.config.tolerant)this.tolerateUnexpectedToken(this.nextToken()),i=this.finalize(this.createNode(),new a.EmptyStatement);else{this.expect(")");var f=this.context.inIteration;this.context.inIteration=!0,i=this.isolateCoverGrammar(this.parseStatement),this.context.inIteration=f}return void 0===e?this.finalize(p,new a.ForStatement(s,r,o,i)):l?this.finalize(p,new a.ForInStatement(e,t,i)):this.finalize(p,new a.ForOfStatement(e,t,i))},e.prototype.parseContinueStatement=function(){var e=this.createNode();this.expectKeyword("continue");var t=null;if(3===this.lookahead.type&&!this.hasLineTerminator){var i=this.parseVariableIdentifier();t=i;var s="$"+i.name;Object.prototype.hasOwnProperty.call(this.context.labelSet,s)||this.throwError(n.Messages.UnknownLabel,i.name)}return this.consumeSemicolon(),null!==t||this.context.inIteration||this.throwError(n.Messages.IllegalContinue),this.finalize(e,new a.ContinueStatement(t))},e.prototype.parseBreakStatement=function(){var e=this.createNode();this.expectKeyword("break");var t=null;if(3===this.lookahead.type&&!this.hasLineTerminator){var i=this.parseVariableIdentifier(),s="$"+i.name;Object.prototype.hasOwnProperty.call(this.context.labelSet,s)||this.throwError(n.Messages.UnknownLabel,i.name),t=i}return this.consumeSemicolon(),null!==t||this.context.inIteration||this.context.inSwitch||this.throwError(n.Messages.IllegalBreak),this.finalize(e,new a.BreakStatement(t))},e.prototype.parseReturnStatement=function(){this.context.inFunctionBody||this.tolerateError(n.Messages.IllegalReturn);var e=this.createNode();this.expectKeyword("return");var t=(this.match(";")||this.match("}")||this.hasLineTerminator||2===this.lookahead.type)&&8!==this.lookahead.type&&10!==this.lookahead.type?null:this.parseExpression();return this.consumeSemicolon(),this.finalize(e,new a.ReturnStatement(t))},e.prototype.parseWithStatement=function(){this.context.strict&&this.tolerateError(n.Messages.StrictModeWith);var e,t=this.createNode();this.expectKeyword("with"),this.expect("(");var i=this.parseExpression();return!this.match(")")&&this.config.tolerant?(this.tolerateUnexpectedToken(this.nextToken()),e=this.finalize(this.createNode(),new a.EmptyStatement)):(this.expect(")"),e=this.parseStatement()),this.finalize(t,new a.WithStatement(i,e))},e.prototype.parseSwitchCase=function(){var e,t=this.createNode();this.matchKeyword("default")?(this.nextToken(),e=null):(this.expectKeyword("case"),e=this.parseExpression()),this.expect(":");for(var i=[];!(this.match("}")||this.matchKeyword("default")||this.matchKeyword("case"));)i.push(this.parseStatementListItem());return this.finalize(t,new a.SwitchCase(e,i))},e.prototype.parseSwitchStatement=function(){var e=this.createNode();this.expectKeyword("switch"),this.expect("(");var t=this.parseExpression();this.expect(")");var i=this.context.inSwitch;this.context.inSwitch=!0;var s=[],r=!1;for(this.expect("{");!this.match("}");){var o=this.parseSwitchCase();null===o.test&&(r&&this.throwError(n.Messages.MultipleDefaultsInSwitch),r=!0),s.push(o)}return this.expect("}"),this.context.inSwitch=i,this.finalize(e,new a.SwitchStatement(t,s))},e.prototype.parseLabelledStatement=function(){var e,t=this.createNode(),i=this.parseExpression();if(i.type===c.Syntax.Identifier&&this.match(":")){this.nextToken();var s=i,r="$"+s.name;Object.prototype.hasOwnProperty.call(this.context.labelSet,r)&&this.throwError(n.Messages.Redeclaration,"Label",s.name),this.context.labelSet[r]=!0;var o=void 0;if(this.matchKeyword("class"))this.tolerateUnexpectedToken(this.lookahead),o=this.parseClassDeclaration();else if(this.matchKeyword("function")){var l=this.lookahead,p=this.parseFunctionDeclaration();this.context.strict?this.tolerateUnexpectedToken(l,n.Messages.StrictFunction):p.generator&&this.tolerateUnexpectedToken(l,n.Messages.GeneratorInLegacyContext),o=p}else o=this.parseStatement();delete this.context.labelSet[r],e=new a.LabeledStatement(s,o)}else this.consumeSemicolon(),e=new a.ExpressionStatement(i);return this.finalize(t,e)},e.prototype.parseThrowStatement=function(){var e=this.createNode();this.expectKeyword("throw"),this.hasLineTerminator&&this.throwError(n.Messages.NewlineAfterThrow);var t=this.parseExpression();return this.consumeSemicolon(),this.finalize(e,new a.ThrowStatement(t))},e.prototype.parseCatchClause=function(){var e=this.createNode();this.expectKeyword("catch"),this.expect("("),this.match(")")&&this.throwUnexpectedToken(this.lookahead);for(var t=[],i=this.parsePattern(t),s={},r=0;r0&&this.tolerateError(n.Messages.BadGetterArity);var s=this.parsePropertyMethod(i);return this.context.allowYield=t,this.finalize(e,new a.FunctionExpression(null,i.params,s,!1))},e.prototype.parseSetterMethod=function(){var e=this.createNode(),t=this.context.allowYield;this.context.allowYield=!0;var i=this.parseFormalParameters();1!==i.params.length?this.tolerateError(n.Messages.BadSetterArity):i.params[0]instanceof a.RestElement&&this.tolerateError(n.Messages.BadSetterRestParameter);var s=this.parsePropertyMethod(i);return this.context.allowYield=t,this.finalize(e,new a.FunctionExpression(null,i.params,s,!1))},e.prototype.parseGeneratorMethod=function(){var e=this.createNode(),t=this.context.allowYield;this.context.allowYield=!0;var i=this.parseFormalParameters();this.context.allowYield=!1;var s=this.parsePropertyMethod(i);return this.context.allowYield=t,this.finalize(e,new a.FunctionExpression(null,i.params,s,!0))},e.prototype.isStartOfExpression=function(){var e=!0,t=this.lookahead.value;switch(this.lookahead.type){case 7:e="["===t||"("===t||"{"===t||"+"===t||"-"===t||"!"===t||"~"===t||"++"===t||"--"===t||"/"===t||"/="===t;break;case 4:e="class"===t||"delete"===t||"function"===t||"let"===t||"new"===t||"super"===t||"this"===t||"typeof"===t||"void"===t||"yield"===t}return e},e.prototype.parseYieldExpression=function(){var e=this.createNode();this.expectKeyword("yield");var t=null,i=!1;if(!this.hasLineTerminator){var s=this.context.allowYield;this.context.allowYield=!1,(i=this.match("*"))?(this.nextToken(),t=this.parseAssignmentExpression()):this.isStartOfExpression()&&(t=this.parseAssignmentExpression()),this.context.allowYield=s}return this.finalize(e,new a.YieldExpression(t,i))},e.prototype.parseClassElement=function(e){var t=this.lookahead,i=this.createNode(),s="",r=null,o=null,c=!1,l=!1,p=!1,A=!1;if(this.match("*"))this.nextToken();else if(c=this.match("["),"static"===(r=this.parseObjectPropertyKey()).name&&(this.qualifiedPropertyName(this.lookahead)||this.match("*"))&&(t=this.lookahead,p=!0,c=this.match("["),this.match("*")?this.nextToken():r=this.parseObjectPropertyKey()),3===t.type&&!this.hasLineTerminator&&"async"===t.value){var u=this.lookahead.value;":"!==u&&"("!==u&&"*"!==u&&(A=!0,t=this.lookahead,r=this.parseObjectPropertyKey(),3===t.type&&"constructor"===t.value&&this.tolerateUnexpectedToken(t,n.Messages.ConstructorIsAsync))}var d=this.qualifiedPropertyName(this.lookahead);return 3===t.type?"get"===t.value&&d?(s="get",c=this.match("["),r=this.parseObjectPropertyKey(),this.context.allowYield=!1,o=this.parseGetterMethod()):"set"===t.value&&d&&(s="set",c=this.match("["),r=this.parseObjectPropertyKey(),o=this.parseSetterMethod()):7===t.type&&"*"===t.value&&d&&(s="init",c=this.match("["),r=this.parseObjectPropertyKey(),o=this.parseGeneratorMethod(),l=!0),!s&&r&&this.match("(")&&(s="init",o=A?this.parsePropertyMethodAsyncFunction():this.parsePropertyMethodFunction(),l=!0),s||this.throwUnexpectedToken(this.lookahead),"init"===s&&(s="method"),c||(p&&this.isPropertyKey(r,"prototype")&&this.throwUnexpectedToken(t,n.Messages.StaticPrototype),!p&&this.isPropertyKey(r,"constructor")&&(("method"!==s||!l||o&&o.generator)&&this.throwUnexpectedToken(t,n.Messages.ConstructorSpecialMethod),e.value?this.throwUnexpectedToken(t,n.Messages.DuplicateConstructor):e.value=!0,s="constructor")),this.finalize(i,new a.MethodDefinition(r,c,o,s,p))},e.prototype.parseClassElementList=function(){var e=[],t={value:!1};for(this.expect("{");!this.match("}");)this.match(";")?this.nextToken():e.push(this.parseClassElement(t));return this.expect("}"),e},e.prototype.parseClassBody=function(){var e=this.createNode(),t=this.parseClassElementList();return this.finalize(e,new a.ClassBody(t))},e.prototype.parseClassDeclaration=function(e){var t=this.createNode(),i=this.context.strict;this.context.strict=!0,this.expectKeyword("class");var s=e&&3!==this.lookahead.type?null:this.parseVariableIdentifier(),r=null;this.matchKeyword("extends")&&(this.nextToken(),r=this.isolateCoverGrammar(this.parseLeftHandSideExpressionAllowCall));var n=this.parseClassBody();return this.context.strict=i,this.finalize(t,new a.ClassDeclaration(s,r,n))},e.prototype.parseClassExpression=function(){var e=this.createNode(),t=this.context.strict;this.context.strict=!0,this.expectKeyword("class");var i=3===this.lookahead.type?this.parseVariableIdentifier():null,s=null;this.matchKeyword("extends")&&(this.nextToken(),s=this.isolateCoverGrammar(this.parseLeftHandSideExpressionAllowCall));var r=this.parseClassBody();return this.context.strict=t,this.finalize(e,new a.ClassExpression(i,s,r))},e.prototype.parseModule=function(){this.context.strict=!0,this.context.isModule=!0,this.scanner.isModule=!0;for(var e=this.createNode(),t=this.parseDirectivePrologues();2!==this.lookahead.type;)t.push(this.parseStatementListItem());return this.finalize(e,new a.Module(t))},e.prototype.parseScript=function(){for(var e=this.createNode(),t=this.parseDirectivePrologues();2!==this.lookahead.type;)t.push(this.parseStatementListItem());return this.finalize(e,new a.Script(t))},e.prototype.parseModuleSpecifier=function(){var e=this.createNode();8!==this.lookahead.type&&this.throwError(n.Messages.InvalidModuleSpecifier);var t=this.nextToken(),i=this.getTokenRaw(t);return this.finalize(e,new a.Literal(t.value,i))},e.prototype.parseImportSpecifier=function(){var e,t,i=this.createNode();return 3===this.lookahead.type?(t=e=this.parseVariableIdentifier(),this.matchContextualKeyword("as")&&(this.nextToken(),t=this.parseVariableIdentifier())):(t=e=this.parseIdentifierName(),this.matchContextualKeyword("as")?(this.nextToken(),t=this.parseVariableIdentifier()):this.throwUnexpectedToken(this.nextToken())),this.finalize(i,new a.ImportSpecifier(t,e))},e.prototype.parseNamedImports=function(){this.expect("{");for(var e=[];!this.match("}");)e.push(this.parseImportSpecifier()),this.match("}")||this.expect(",");return this.expect("}"),e},e.prototype.parseImportDefaultSpecifier=function(){var e=this.createNode(),t=this.parseIdentifierName();return this.finalize(e,new a.ImportDefaultSpecifier(t))},e.prototype.parseImportNamespaceSpecifier=function(){var e=this.createNode();this.expect("*"),this.matchContextualKeyword("as")||this.throwError(n.Messages.NoAsAfterImportNamespace),this.nextToken();var t=this.parseIdentifierName();return this.finalize(e,new a.ImportNamespaceSpecifier(t))},e.prototype.parseImportDeclaration=function(){this.context.inFunctionBody&&this.throwError(n.Messages.IllegalImportDeclaration);var e,t=this.createNode();this.expectKeyword("import");var i=[];if(8===this.lookahead.type)e=this.parseModuleSpecifier();else{if(this.match("{")?i=i.concat(this.parseNamedImports()):this.match("*")?i.push(this.parseImportNamespaceSpecifier()):this.isIdentifierName(this.lookahead)&&!this.matchKeyword("default")?(i.push(this.parseImportDefaultSpecifier()),this.match(",")&&(this.nextToken(),this.match("*")?i.push(this.parseImportNamespaceSpecifier()):this.match("{")?i=i.concat(this.parseNamedImports()):this.throwUnexpectedToken(this.lookahead))):this.throwUnexpectedToken(this.nextToken()),!this.matchContextualKeyword("from")){var s=this.lookahead.value?n.Messages.UnexpectedToken:n.Messages.MissingFromClause;this.throwError(s,this.lookahead.value)}this.nextToken(),e=this.parseModuleSpecifier()}return this.consumeSemicolon(),this.finalize(t,new a.ImportDeclaration(i,e))},e.prototype.parseExportSpecifier=function(){var e=this.createNode(),t=this.parseIdentifierName(),i=t;return this.matchContextualKeyword("as")&&(this.nextToken(),i=this.parseIdentifierName()),this.finalize(e,new a.ExportSpecifier(t,i))},e.prototype.parseExportDeclaration=function(){this.context.inFunctionBody&&this.throwError(n.Messages.IllegalExportDeclaration);var e,t=this.createNode();if(this.expectKeyword("export"),this.matchKeyword("default"))if(this.nextToken(),this.matchKeyword("function")){var i=this.parseFunctionDeclaration(!0);e=this.finalize(t,new a.ExportDefaultDeclaration(i))}else this.matchKeyword("class")?(i=this.parseClassDeclaration(!0),e=this.finalize(t,new a.ExportDefaultDeclaration(i))):this.matchContextualKeyword("async")?(i=this.matchAsyncFunction()?this.parseFunctionDeclaration(!0):this.parseAssignmentExpression(),e=this.finalize(t,new a.ExportDefaultDeclaration(i))):(this.matchContextualKeyword("from")&&this.throwError(n.Messages.UnexpectedToken,this.lookahead.value),i=this.match("{")?this.parseObjectInitializer():this.match("[")?this.parseArrayInitializer():this.parseAssignmentExpression(),this.consumeSemicolon(),e=this.finalize(t,new a.ExportDefaultDeclaration(i)));else if(this.match("*")){if(this.nextToken(),!this.matchContextualKeyword("from")){var s=this.lookahead.value?n.Messages.UnexpectedToken:n.Messages.MissingFromClause;this.throwError(s,this.lookahead.value)}this.nextToken();var r=this.parseModuleSpecifier();this.consumeSemicolon(),e=this.finalize(t,new a.ExportAllDeclaration(r))}else if(4===this.lookahead.type){switch(i=void 0,this.lookahead.value){case"let":case"const":i=this.parseLexicalDeclaration({inFor:!1});break;case"var":case"class":case"function":i=this.parseStatementListItem();break;default:this.throwUnexpectedToken(this.lookahead)}e=this.finalize(t,new a.ExportNamedDeclaration(i,[],null))}else if(this.matchAsyncFunction())i=this.parseFunctionDeclaration(),e=this.finalize(t,new a.ExportNamedDeclaration(i,[],null));else{var o=[],c=null,l=!1;for(this.expect("{");!this.match("}");)l=l||this.matchKeyword("default"),o.push(this.parseExportSpecifier()),this.match("}")||this.expect(",");this.expect("}"),this.matchContextualKeyword("from")?(this.nextToken(),c=this.parseModuleSpecifier(),this.consumeSemicolon()):l?(s=this.lookahead.value?n.Messages.UnexpectedToken:n.Messages.MissingFromClause,this.throwError(s,this.lookahead.value)):this.consumeSemicolon(),e=this.finalize(t,new a.ExportNamedDeclaration(null,o,c))}return e},e}();t.Parser=A},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.assert=function(e,t){if(!e)throw new Error("ASSERT: "+t)}},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(){this.errors=[],this.tolerant=!1}return e.prototype.recordError=function(e){this.errors.push(e)},e.prototype.tolerate=function(e){if(!this.tolerant)throw e;this.recordError(e)},e.prototype.constructError=function(e,t){var i=new Error(e);try{throw i}catch(e){Object.create&&Object.defineProperty&&(i=Object.create(e),Object.defineProperty(i,"column",{value:t}))}return i},e.prototype.createError=function(e,t,i,s){var r="Line "+t+": "+s,n=this.constructError(r,i);return n.index=e,n.lineNumber=t,n.description=s,n},e.prototype.throwError=function(e,t,i,s){throw this.createError(e,t,i,s)},e.prototype.tolerateError=function(e,t,i,s){var r=this.createError(e,t,i,s);if(!this.tolerant)throw r;this.recordError(r)},e}();t.ErrorHandler=i},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Messages={BadGetterArity:"Getter must not have any formal parameters",BadSetterArity:"Setter must have exactly one formal parameter",BadSetterRestParameter:"Setter function argument must not be a rest parameter",ConstructorIsAsync:"Class constructor may not be an async method",ConstructorSpecialMethod:"Class constructor may not be an accessor",DeclarationMissingInitializer:"Missing initializer in %0 declaration",DefaultRestParameter:"Unexpected token =",DuplicateBinding:"Duplicate binding %0",DuplicateConstructor:"A class may only have one constructor",DuplicateProtoProperty:"Duplicate __proto__ fields are not allowed in object literals",ForInOfLoopInitializer:"%0 loop variable declaration may not have an initializer",GeneratorInLegacyContext:"Generator declarations are not allowed in legacy contexts",IllegalBreak:"Illegal break statement",IllegalContinue:"Illegal continue statement",IllegalExportDeclaration:"Unexpected token",IllegalImportDeclaration:"Unexpected token",IllegalLanguageModeDirective:"Illegal 'use strict' directive in function with non-simple parameter list",IllegalReturn:"Illegal return statement",InvalidEscapedReservedWord:"Keyword must not contain escaped characters",InvalidHexEscapeSequence:"Invalid hexadecimal escape sequence",InvalidLHSInAssignment:"Invalid left-hand side in assignment",InvalidLHSInForIn:"Invalid left-hand side in for-in",InvalidLHSInForLoop:"Invalid left-hand side in for-loop",InvalidModuleSpecifier:"Unexpected token",InvalidRegExp:"Invalid regular expression",LetInLexicalBinding:"let is disallowed as a lexically bound name",MissingFromClause:"Unexpected token",MultipleDefaultsInSwitch:"More than one default clause in switch statement",NewlineAfterThrow:"Illegal newline after throw",NoAsAfterImportNamespace:"Unexpected token",NoCatchOrFinally:"Missing catch or finally after try",ParameterAfterRestParameter:"Rest parameter must be last formal parameter",Redeclaration:"%0 '%1' has already been declared",StaticPrototype:"Classes may not have static property named prototype",StrictCatchVariable:"Catch variable may not be eval or arguments in strict mode",StrictDelete:"Delete of an unqualified identifier in strict mode.",StrictFunction:"In strict mode code, functions can only be declared at top level or inside a block",StrictFunctionName:"Function name may not be eval or arguments in strict mode",StrictLHSAssignment:"Assignment to eval or arguments is not allowed in strict mode",StrictLHSPostfix:"Postfix increment/decrement may not have eval or arguments operand in strict mode",StrictLHSPrefix:"Prefix increment/decrement may not have eval or arguments operand in strict mode",StrictModeWith:"Strict mode code may not include a with statement",StrictOctalLiteral:"Octal literals are not allowed in strict mode.",StrictParamDupe:"Strict mode function may not have duplicate parameter names",StrictParamName:"Parameter name eval or arguments is not allowed in strict mode",StrictReservedWord:"Use of future reserved word in strict mode",StrictVarName:"Variable name may not be eval or arguments in strict mode",TemplateOctalLiteral:"Octal literals are not allowed in template strings.",UnexpectedEOS:"Unexpected end of input",UnexpectedIdentifier:"Unexpected identifier",UnexpectedNumber:"Unexpected number",UnexpectedReserved:"Unexpected reserved word",UnexpectedString:"Unexpected string",UnexpectedTemplate:"Unexpected quasi %0",UnexpectedToken:"Unexpected token %0",UnexpectedTokenIllegal:"Unexpected token ILLEGAL",UnknownLabel:"Undefined label '%0'",UnterminatedRegExp:"Invalid regular expression: missing /"}},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var s=i(9),r=i(4),n=i(11);function a(e){return"0123456789abcdef".indexOf(e.toLowerCase())}function o(e){return"01234567".indexOf(e)}var c=function(){function e(e,t){this.source=e,this.errorHandler=t,this.trackComment=!1,this.isModule=!1,this.length=e.length,this.index=0,this.lineNumber=e.length>0?1:0,this.lineStart=0,this.curlyStack=[]}return e.prototype.saveState=function(){return{index:this.index,lineNumber:this.lineNumber,lineStart:this.lineStart}},e.prototype.restoreState=function(e){this.index=e.index,this.lineNumber=e.lineNumber,this.lineStart=e.lineStart},e.prototype.eof=function(){return this.index>=this.length},e.prototype.throwUnexpectedToken=function(e){return void 0===e&&(e=n.Messages.UnexpectedTokenIllegal),this.errorHandler.throwError(this.index,this.lineNumber,this.index-this.lineStart+1,e)},e.prototype.tolerateUnexpectedToken=function(e){void 0===e&&(e=n.Messages.UnexpectedTokenIllegal),this.errorHandler.tolerateError(this.index,this.lineNumber,this.index-this.lineStart+1,e)},e.prototype.skipSingleLineComment=function(e){var t,i,s=[];for(this.trackComment&&(s=[],t=this.index-e,i={start:{line:this.lineNumber,column:this.index-this.lineStart-e},end:{}});!this.eof();){var n=this.source.charCodeAt(this.index);if(++this.index,r.Character.isLineTerminator(n)){if(this.trackComment){i.end={line:this.lineNumber,column:this.index-this.lineStart-1};var a={multiLine:!1,slice:[t+e,this.index-1],range:[t,this.index-1],loc:i};s.push(a)}return 13===n&&10===this.source.charCodeAt(this.index)&&++this.index,++this.lineNumber,this.lineStart=this.index,s}}return this.trackComment&&(i.end={line:this.lineNumber,column:this.index-this.lineStart},a={multiLine:!1,slice:[t+e,this.index],range:[t,this.index],loc:i},s.push(a)),s},e.prototype.skipMultiLineComment=function(){var e,t,i=[];for(this.trackComment&&(i=[],e=this.index-2,t={start:{line:this.lineNumber,column:this.index-this.lineStart-2},end:{}});!this.eof();){var s=this.source.charCodeAt(this.index);if(r.Character.isLineTerminator(s))13===s&&10===this.source.charCodeAt(this.index+1)&&++this.index,++this.lineNumber,++this.index,this.lineStart=this.index;else if(42===s){if(47===this.source.charCodeAt(this.index+1)){if(this.index+=2,this.trackComment){t.end={line:this.lineNumber,column:this.index-this.lineStart};var n={multiLine:!0,slice:[e+2,this.index-2],range:[e,this.index],loc:t};i.push(n)}return i}++this.index}else++this.index}return this.trackComment&&(t.end={line:this.lineNumber,column:this.index-this.lineStart},n={multiLine:!0,slice:[e+2,this.index],range:[e,this.index],loc:t},i.push(n)),this.tolerateUnexpectedToken(),i},e.prototype.scanComments=function(){var e;this.trackComment&&(e=[]);for(var t=0===this.index;!this.eof();){var i=this.source.charCodeAt(this.index);if(r.Character.isWhiteSpace(i))++this.index;else if(r.Character.isLineTerminator(i))++this.index,13===i&&10===this.source.charCodeAt(this.index)&&++this.index,++this.lineNumber,this.lineStart=this.index,t=!0;else if(47===i)if(47===(i=this.source.charCodeAt(this.index+1))){this.index+=2;var s=this.skipSingleLineComment(2);this.trackComment&&(e=e.concat(s)),t=!0}else{if(42!==i)break;this.index+=2,s=this.skipMultiLineComment(),this.trackComment&&(e=e.concat(s))}else if(t&&45===i){if(45!==this.source.charCodeAt(this.index+1)||62!==this.source.charCodeAt(this.index+2))break;this.index+=3,s=this.skipSingleLineComment(3),this.trackComment&&(e=e.concat(s))}else{if(60!==i||this.isModule)break;if("!--"!==this.source.slice(this.index+1,this.index+4))break;this.index+=4,s=this.skipSingleLineComment(4),this.trackComment&&(e=e.concat(s))}}return e},e.prototype.isFutureReservedWord=function(e){switch(e){case"enum":case"export":case"import":case"super":return!0;default:return!1}},e.prototype.isStrictModeReservedWord=function(e){switch(e){case"implements":case"interface":case"package":case"private":case"protected":case"public":case"static":case"yield":case"let":return!0;default:return!1}},e.prototype.isRestrictedWord=function(e){return"eval"===e||"arguments"===e},e.prototype.isKeyword=function(e){switch(e.length){case 2:return"if"===e||"in"===e||"do"===e;case 3:return"var"===e||"for"===e||"new"===e||"try"===e||"let"===e;case 4:return"this"===e||"else"===e||"case"===e||"void"===e||"with"===e||"enum"===e;case 5:return"while"===e||"break"===e||"catch"===e||"throw"===e||"const"===e||"yield"===e||"class"===e||"super"===e;case 6:return"return"===e||"typeof"===e||"delete"===e||"switch"===e||"export"===e||"import"===e;case 7:return"default"===e||"finally"===e||"extends"===e;case 8:return"function"===e||"continue"===e||"debugger"===e;case 10:return"instanceof"===e;default:return!1}},e.prototype.codePointAt=function(e){var t=this.source.charCodeAt(e);if(t>=55296&&t<=56319){var i=this.source.charCodeAt(e+1);i>=56320&&i<=57343&&(t=1024*(t-55296)+i-56320+65536)}return t},e.prototype.scanHexEscape=function(e){for(var t="u"===e?4:2,i=0,s=0;s1114111||"}"!==e)&&this.throwUnexpectedToken(),r.Character.fromCodePoint(t)},e.prototype.getIdentifier=function(){for(var e=this.index++;!this.eof();){var t=this.source.charCodeAt(this.index);if(92===t)return this.index=e,this.getComplexIdentifier();if(t>=55296&&t<57343)return this.index=e,this.getComplexIdentifier();if(!r.Character.isIdentifierPart(t))break;++this.index}return this.source.slice(e,this.index)},e.prototype.getComplexIdentifier=function(){var e,t=this.codePointAt(this.index),i=r.Character.fromCodePoint(t);for(this.index+=i.length,92===t&&(117!==this.source.charCodeAt(this.index)&&this.throwUnexpectedToken(),++this.index,"{"===this.source[this.index]?(++this.index,e=this.scanUnicodeCodePointEscape()):null!==(e=this.scanHexEscape("u"))&&"\\"!==e&&r.Character.isIdentifierStart(e.charCodeAt(0))||this.throwUnexpectedToken(),i=e);!this.eof()&&(t=this.codePointAt(this.index),r.Character.isIdentifierPart(t));)i+=e=r.Character.fromCodePoint(t),this.index+=e.length,92===t&&(i=i.substr(0,i.length-1),117!==this.source.charCodeAt(this.index)&&this.throwUnexpectedToken(),++this.index,"{"===this.source[this.index]?(++this.index,e=this.scanUnicodeCodePointEscape()):null!==(e=this.scanHexEscape("u"))&&"\\"!==e&&r.Character.isIdentifierPart(e.charCodeAt(0))||this.throwUnexpectedToken(),i+=e);return i},e.prototype.octalToDecimal=function(e){var t="0"!==e,i=o(e);return!this.eof()&&r.Character.isOctalDigit(this.source.charCodeAt(this.index))&&(t=!0,i=8*i+o(this.source[this.index++]),"0123".indexOf(e)>=0&&!this.eof()&&r.Character.isOctalDigit(this.source.charCodeAt(this.index))&&(i=8*i+o(this.source[this.index++]))),{code:i,octal:t}},e.prototype.scanIdentifier=function(){var e,t=this.index,i=92===this.source.charCodeAt(t)?this.getComplexIdentifier():this.getIdentifier();if(3!=(e=1===i.length?3:this.isKeyword(i)?4:"null"===i?5:"true"===i||"false"===i?1:3)&&t+i.length!==this.index){var s=this.index;this.index=t,this.tolerateUnexpectedToken(n.Messages.InvalidEscapedReservedWord),this.index=s}return{type:e,value:i,lineNumber:this.lineNumber,lineStart:this.lineStart,start:t,end:this.index}},e.prototype.scanPunctuator=function(){var e=this.index,t=this.source[this.index];switch(t){case"(":case"{":"{"===t&&this.curlyStack.push("{"),++this.index;break;case".":++this.index,"."===this.source[this.index]&&"."===this.source[this.index+1]&&(this.index+=2,t="...");break;case"}":++this.index,this.curlyStack.pop();break;case")":case";":case",":case"[":case"]":case":":case"?":case"~":++this.index;break;default:">>>="===(t=this.source.substr(this.index,4))?this.index+=4:"==="===(t=t.substr(0,3))||"!=="===t||">>>"===t||"<<="===t||">>="===t||"**="===t?this.index+=3:"&&"===(t=t.substr(0,2))||"||"===t||"=="===t||"!="===t||"+="===t||"-="===t||"*="===t||"/="===t||"++"===t||"--"===t||"<<"===t||">>"===t||"&="===t||"|="===t||"^="===t||"%="===t||"<="===t||">="===t||"=>"===t||"**"===t?this.index+=2:(t=this.source[this.index],"<>=!+-*%&|^/".indexOf(t)>=0&&++this.index)}return this.index===e&&this.throwUnexpectedToken(),{type:7,value:t,lineNumber:this.lineNumber,lineStart:this.lineStart,start:e,end:this.index}},e.prototype.scanHexLiteral=function(e){for(var t="";!this.eof()&&r.Character.isHexDigit(this.source.charCodeAt(this.index));)t+=this.source[this.index++];return 0===t.length&&this.throwUnexpectedToken(),r.Character.isIdentifierStart(this.source.charCodeAt(this.index))&&this.throwUnexpectedToken(),{type:6,value:parseInt("0x"+t,16),lineNumber:this.lineNumber,lineStart:this.lineStart,start:e,end:this.index}},e.prototype.scanBinaryLiteral=function(e){for(var t,i="";!this.eof()&&("0"===(t=this.source[this.index])||"1"===t);)i+=this.source[this.index++];return 0===i.length&&this.throwUnexpectedToken(),this.eof()||(t=this.source.charCodeAt(this.index),(r.Character.isIdentifierStart(t)||r.Character.isDecimalDigit(t))&&this.throwUnexpectedToken()),{type:6,value:parseInt(i,2),lineNumber:this.lineNumber,lineStart:this.lineStart,start:e,end:this.index}},e.prototype.scanOctalLiteral=function(e,t){var i="",s=!1;for(r.Character.isOctalDigit(e.charCodeAt(0))?(s=!0,i="0"+this.source[this.index++]):++this.index;!this.eof()&&r.Character.isOctalDigit(this.source.charCodeAt(this.index));)i+=this.source[this.index++];return s||0!==i.length||this.throwUnexpectedToken(),(r.Character.isIdentifierStart(this.source.charCodeAt(this.index))||r.Character.isDecimalDigit(this.source.charCodeAt(this.index)))&&this.throwUnexpectedToken(),{type:6,value:parseInt(i,8),octal:s,lineNumber:this.lineNumber,lineStart:this.lineStart,start:t,end:this.index}},e.prototype.isImplicitOctalLiteral=function(){for(var e=this.index+1;e=0&&(i=i.replace(/\\u\{([0-9a-fA-F]+)\}|\\u([a-fA-F0-9]{4})/g,(function(e,t,i){var r=parseInt(t||i,16);return r>1114111&&s.throwUnexpectedToken(n.Messages.InvalidRegExp),r<=65535?String.fromCharCode(r):"￿"})).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,"￿"));try{RegExp(i)}catch(e){this.throwUnexpectedToken(n.Messages.InvalidRegExp)}try{return new RegExp(e,t)}catch(e){return null}},e.prototype.scanRegExpBody=function(){var e=this.source[this.index];s.assert("/"===e,"Regular expression literal must start with a slash");for(var t=this.source[this.index++],i=!1,a=!1;!this.eof();)if(t+=e=this.source[this.index++],"\\"===e)e=this.source[this.index++],r.Character.isLineTerminator(e.charCodeAt(0))&&this.throwUnexpectedToken(n.Messages.UnterminatedRegExp),t+=e;else if(r.Character.isLineTerminator(e.charCodeAt(0)))this.throwUnexpectedToken(n.Messages.UnterminatedRegExp);else if(i)"]"===e&&(i=!1);else{if("/"===e){a=!0;break}"["===e&&(i=!0)}return a||this.throwUnexpectedToken(n.Messages.UnterminatedRegExp),t.substr(1,t.length-2)},e.prototype.scanRegExpFlags=function(){for(var e="";!this.eof();){var t=this.source[this.index];if(!r.Character.isIdentifierPart(t.charCodeAt(0)))break;if(++this.index,"\\"!==t||this.eof())e+=t;else if("u"===(t=this.source[this.index])){++this.index;var i=this.index,s=this.scanHexEscape("u");if(null!==s)for(e+=s;i=55296&&e<57343&&r.Character.isIdentifierStart(this.codePointAt(this.index))?this.scanIdentifier():this.scanPunctuator()},e}();t.Scanner=c},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TokenName={},t.TokenName[1]="Boolean",t.TokenName[2]="",t.TokenName[3]="Identifier",t.TokenName[4]="Keyword",t.TokenName[5]="Null",t.TokenName[6]="Numeric",t.TokenName[7]="Punctuator",t.TokenName[8]="String",t.TokenName[9]="RegularExpression",t.TokenName[10]="Template"},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.XHTMLEntities={quot:'"',amp:"&",apos:"'",gt:">",nbsp:" ",iexcl:"¡",cent:"¢",pound:"£",curren:"¤",yen:"¥",brvbar:"¦",sect:"§",uml:"¨",copy:"©",ordf:"ª",laquo:"«",not:"¬",shy:"­",reg:"®",macr:"¯",deg:"°",plusmn:"±",sup2:"²",sup3:"³",acute:"´",micro:"µ",para:"¶",middot:"·",cedil:"¸",sup1:"¹",ordm:"º",raquo:"»",frac14:"¼",frac12:"½",frac34:"¾",iquest:"¿",Agrave:"À",Aacute:"Á",Acirc:"Â",Atilde:"Ã",Auml:"Ä",Aring:"Å",AElig:"Æ",Ccedil:"Ç",Egrave:"È",Eacute:"É",Ecirc:"Ê",Euml:"Ë",Igrave:"Ì",Iacute:"Í",Icirc:"Î",Iuml:"Ï",ETH:"Ð",Ntilde:"Ñ",Ograve:"Ò",Oacute:"Ó",Ocirc:"Ô",Otilde:"Õ",Ouml:"Ö",times:"×",Oslash:"Ø",Ugrave:"Ù",Uacute:"Ú",Ucirc:"Û",Uuml:"Ü",Yacute:"Ý",THORN:"Þ",szlig:"ß",agrave:"à",aacute:"á",acirc:"â",atilde:"ã",auml:"ä",aring:"å",aelig:"æ",ccedil:"ç",egrave:"è",eacute:"é",ecirc:"ê",euml:"ë",igrave:"ì",iacute:"í",icirc:"î",iuml:"ï",eth:"ð",ntilde:"ñ",ograve:"ò",oacute:"ó",ocirc:"ô",otilde:"õ",ouml:"ö",divide:"÷",oslash:"ø",ugrave:"ù",uacute:"ú",ucirc:"û",uuml:"ü",yacute:"ý",thorn:"þ",yuml:"ÿ",OElig:"Œ",oelig:"œ",Scaron:"Š",scaron:"š",Yuml:"Ÿ",fnof:"ƒ",circ:"ˆ",tilde:"˜",Alpha:"Α",Beta:"Β",Gamma:"Γ",Delta:"Δ",Epsilon:"Ε",Zeta:"Ζ",Eta:"Η",Theta:"Θ",Iota:"Ι",Kappa:"Κ",Lambda:"Λ",Mu:"Μ",Nu:"Ν",Xi:"Ξ",Omicron:"Ο",Pi:"Π",Rho:"Ρ",Sigma:"Σ",Tau:"Τ",Upsilon:"Υ",Phi:"Φ",Chi:"Χ",Psi:"Ψ",Omega:"Ω",alpha:"α",beta:"β",gamma:"γ",delta:"δ",epsilon:"ε",zeta:"ζ",eta:"η",theta:"θ",iota:"ι",kappa:"κ",lambda:"λ",mu:"μ",nu:"ν",xi:"ξ",omicron:"ο",pi:"π",rho:"ρ",sigmaf:"ς",sigma:"σ",tau:"τ",upsilon:"υ",phi:"φ",chi:"χ",psi:"ψ",omega:"ω",thetasym:"ϑ",upsih:"ϒ",piv:"ϖ",ensp:" ",emsp:" ",thinsp:" ",zwnj:"‌",zwj:"‍",lrm:"‎",rlm:"‏",ndash:"–",mdash:"—",lsquo:"‘",rsquo:"’",sbquo:"‚",ldquo:"“",rdquo:"”",bdquo:"„",dagger:"†",Dagger:"‡",bull:"•",hellip:"…",permil:"‰",prime:"′",Prime:"″",lsaquo:"‹",rsaquo:"›",oline:"‾",frasl:"⁄",euro:"€",image:"ℑ",weierp:"℘",real:"ℜ",trade:"™",alefsym:"ℵ",larr:"←",uarr:"↑",rarr:"→",darr:"↓",harr:"↔",crarr:"↵",lArr:"⇐",uArr:"⇑",rArr:"⇒",dArr:"⇓",hArr:"⇔",forall:"∀",part:"∂",exist:"∃",empty:"∅",nabla:"∇",isin:"∈",notin:"∉",ni:"∋",prod:"∏",sum:"∑",minus:"−",lowast:"∗",radic:"√",prop:"∝",infin:"∞",ang:"∠",and:"∧",or:"∨",cap:"∩",cup:"∪",int:"∫",there4:"∴",sim:"∼",cong:"≅",asymp:"≈",ne:"≠",equiv:"≡",le:"≤",ge:"≥",sub:"⊂",sup:"⊃",nsub:"⊄",sube:"⊆",supe:"⊇",oplus:"⊕",otimes:"⊗",perp:"⊥",sdot:"⋅",lceil:"⌈",rceil:"⌉",lfloor:"⌊",rfloor:"⌋",loz:"◊",spades:"♠",clubs:"♣",hearts:"♥",diams:"♦",lang:"⟨",rang:"⟩"}},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var s=i(10),r=i(12),n=i(13),a=function(){function e(){this.values=[],this.curly=this.paren=-1}return e.prototype.beforeFunctionExpression=function(e){return["(","{","[","in","typeof","instanceof","new","return","case","delete","throw","void","=","+=","-=","*=","**=","/=","%=","<<=",">>=",">>>=","&=","|=","^=",",","+","-","*","**","/","%","++","--","<<",">>",">>>","&","|","^","!","~","&&","||","?",":","===","==",">=","<=","<",">","!=","!=="].indexOf(e)>=0},e.prototype.isRegexStart=function(){var e=this.values[this.values.length-1],t=null!==e;switch(e){case"this":case"]":t=!1;break;case")":var i=this.values[this.paren-1];t="if"===i||"while"===i||"for"===i||"with"===i;break;case"}":if(t=!1,"function"===this.values[this.curly-3])t=!!(s=this.values[this.curly-4])&&!this.beforeFunctionExpression(s);else if("function"===this.values[this.curly-4]){var s;t=!(s=this.values[this.curly-5])||!this.beforeFunctionExpression(s)}}return t},e.prototype.push=function(e){7===e.type||4===e.type?("{"===e.value?this.curly=this.values.length:"("===e.value&&(this.paren=this.values.length),this.values.push(e.value)):this.values.push(null)},e}(),o=function(){function e(e,t){this.errorHandler=new s.ErrorHandler,this.errorHandler.tolerant=!!t&&"boolean"==typeof t.tolerant&&t.tolerant,this.scanner=new r.Scanner(e,this.errorHandler),this.scanner.trackComment=!!t&&"boolean"==typeof t.comment&&t.comment,this.trackRange=!!t&&"boolean"==typeof t.range&&t.range,this.trackLoc=!!t&&"boolean"==typeof t.loc&&t.loc,this.buffer=[],this.reader=new a}return e.prototype.errors=function(){return this.errorHandler.errors},e.prototype.getNextToken=function(){if(0===this.buffer.length){var e=this.scanner.scanComments();if(this.scanner.trackComment)for(var t=0;t{"use strict";var t=Object.prototype.hasOwnProperty,i="~";function s(){}function r(e,t,i){this.fn=e,this.context=t,this.once=i||!1}function n(e,t,s,n,a){if("function"!=typeof s)throw new TypeError("The listener must be a function");var o=new r(s,n||e,a),c=i?i+t:t;return e._events[c]?e._events[c].fn?e._events[c]=[e._events[c],o]:e._events[c].push(o):(e._events[c]=o,e._eventsCount++),e}function a(e,t){0==--e._eventsCount?e._events=new s:delete e._events[t]}function o(){this._events=new s,this._eventsCount=0}Object.create&&(s.prototype=Object.create(null),(new s).__proto__||(i=!1)),o.prototype.eventNames=function(){var e,s,r=[];if(0===this._eventsCount)return r;for(s in e=this._events)t.call(e,s)&&r.push(i?s.slice(1):s);return Object.getOwnPropertySymbols?r.concat(Object.getOwnPropertySymbols(e)):r},o.prototype.listeners=function(e){var t=i?i+e:e,s=this._events[t];if(!s)return[];if(s.fn)return[s.fn];for(var r=0,n=s.length,a=new Array(n);r{"use strict";var t=Object.prototype.hasOwnProperty,i="~";function s(){}function r(e,t,i){this.fn=e,this.context=t,this.once=i||!1}function n(e,t,s,n,a){if("function"!=typeof s)throw new TypeError("The listener must be a function");var o=new r(s,n||e,a),c=i?i+t:t;return e._events[c]?e._events[c].fn?e._events[c]=[e._events[c],o]:e._events[c].push(o):(e._events[c]=o,e._eventsCount++),e}function a(e,t){0==--e._eventsCount?e._events=new s:delete e._events[t]}function o(){this._events=new s,this._eventsCount=0}Object.create&&(s.prototype=Object.create(null),(new s).__proto__||(i=!1)),o.prototype.eventNames=function(){var e,s,r=[];if(0===this._eventsCount)return r;for(s in e=this._events)t.call(e,s)&&r.push(i?s.slice(1):s);return Object.getOwnPropertySymbols?r.concat(Object.getOwnPropertySymbols(e)):r},o.prototype.listeners=function(e){var t=i?i+e:e,s=this._events[t];if(!s)return[];if(s.fn)return[s.fn];for(var r=0,n=s.length,a=new Array(n);r{"use strict";var s=i(60195);function r(e,t){for(var i in t)n(t,i)&&(e[i]=t[i])}function n(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e){s(e)||(e={});for(var t=arguments.length,i=1;i{"use strict";const t=(()=>{const e=0,t=1,i=2,s={},r={font:"Standard",fontPath:"./fonts"};function n(e,t,i){return e===t&&e!==i&&e}function a(e,t){let i="|/\\[]{}()<>";if("_"===e){if(-1!==i.indexOf(t))return t}else if("_"===t&&-1!==i.indexOf(e))return e;return!1}function o(e,t){let i="| /\\ [] {} () <>",s=i.indexOf(e),r=i.indexOf(t);if(-1!==s&&-1!==r&&s!==r&&1!==Math.abs(s-r)){const e=Math.max(s,r);return i.substring(e,e+1)}return!1}function c(e,t){let i="[] {} ()",s=i.indexOf(e),r=i.indexOf(t);return-1!==s&&-1!==r&&Math.abs(s-r)<=1&&"|"}function l(e,t){let i="/\\ \\/ ><",s=i.indexOf(e),r=i.indexOf(t);return-1!==s&&-1!==r&&r-s==1&&{0:"|",3:"Y",6:"X"}[s]}function p(e,t,i){return e===i&&t===i&&i}function A(e,t){return e===t&&e}function u(e,t){let i="|/\\[]{}()<>";if("_"===e){if(-1!==i.indexOf(t))return t}else if("_"===t&&-1!==i.indexOf(e))return e;return!1}function d(e,t){let i="| /\\ [] {} () <>",s=i.indexOf(e),r=i.indexOf(t);if(-1!==s&&-1!==r&&s!==r&&1!==Math.abs(s-r)){const e=Math.max(s,r);return i.substring(e,e+1)}return!1}function h(e,t){return("-"===e&&"_"===t||"_"===e&&"-"===t)&&"="}function m(e,t){return"|"===e&&"|"===t&&"|"}function g(e,t,i){return" "===t||""===t||t===i&&" "!==e?e:t}function f(s,r,n){if(n.fittingRules.vLayout===e)return"invalid";let a,o,c,l,p=Math.min(s.length,r.length),g=!1;if(0===p)return"invalid";for(a=0;an?C(t,r-n):n>r&&C(e,n-r),s=function(e,t,i){let s,r,n,a,o,c,l=e.length,p=e.length,A=(t.length,1);for(;A<=l;){for(s=e.slice(Math.max(0,p-A),p),r=t.slice(0,Math.min(l,A)),n=r.length,c="",a=0;a=l?A[r]:E(A[r],u[r],s),d.push(a);return o=t.slice(Math.min(i,l),l),[].concat(p,d,o)}(e,t,s,i)}function v(s,r,A){if(A.fittingRules.hLayout===e)return 0;let u,d,h,m,g,f=s.length,E=r.length,C=f,y=1,v=!1,w=!1;if(0===f)return 0;e:for(;y<=C;){const e=f-y;for(d=s.substring(e,e+y),h=r.substring(0,Math.min(y,E)),u=0;u=y?"":w.substring(r,r+Math.max(0,y-r)),I[u]=m+f+E}return I}function I(e){let t,i=[];for(t=0;t0&&s.whitespaceBreak&&(p={chars:[],overlap:g}),1===s.printDirection&&(t=t.split("").reverse().join("")),c=t.length,r=0;r0&&(s.whitespaceBreak?(d=b(p.chars.concat([{fig:n,overlap:g}]),f,s),h=b(C.concat([{fig:d,overlap:p.overlap}]),f,s),l=B(h)):(h=w(o,n,g,s),l=B(h)),l>=s.width&&r>0&&(s.whitespaceBreak?(o=b(C.slice(0,-1),f,s),C.length>1&&(E.push(o),o=I(f)),C=[]):(E.push(o),o=I(f)))),s.width>0&&s.whitespaceBreak&&(u&&r!==c-1||p.chars.push({fig:n,overlap:g}),u||r===c-1)){for(m=null;h=b(p.chars,f,s),l=B(h),l>=s.width;)m=Q(p.chars,f,s),p={chars:m.chars},E.push(m.outputFigText);l>0&&(m?C.push({fig:h,overlap:1}):C.push({fig:h,overlap:p.overlap})),u&&(C.push({fig:n,overlap:g}),o=I(f)),r===c-1&&(o=b(C,f,s)),p={chars:[],overlap:g};continue}o=w(o,n,g,s)}return B(o)>0&&E.push(o),!0!==s.showHardBlanks&&E.forEach((function(e){for(c=e.length,a=0;a{S.loadFont(s,(function(a,o){if(a)return n(a),void(i&&i(a));const c=k(s,D(o,t),e);r(c),i&&i(null,c)}))}))},S.textSync=function(e,t){let i="";e+="","string"==typeof t?(i=t,t={}):i=(t=t||{}).font||r.font;var s=D(S.loadFontSync(i),t);return k(i,s,e)},S.metadata=function(e,t){e+="",S.loadFont(e,(function(i,r){i?t(i):t(null,r,s[e].comment)}))},S.defaults=function(e){if("object"==typeof e&&null!==e)for(var t in e)e.hasOwnProperty(t)&&(r[t]=e[t]);return JSON.parse(JSON.stringify(r))},S.parseFont=function(r,n){n=n.replace(/\r\n/g,"\n").replace(/\r/g,"\n"),s[r]={};var a=n.split("\n"),o=a.splice(0,1)[0].split(" "),c=s[r],l={};if(l.hardBlank=o[0].substr(5,1),l.height=parseInt(o[1],10),l.baseline=parseInt(o[2],10),l.maxLength=parseInt(o[3],10),l.oldLayout=parseInt(o[4],10),l.numCommentLines=parseInt(o[5],10),l.printDirection=o.length>=6?parseInt(o[6],10):0,l.fullLayout=o.length>=7?parseInt(o[7],10):null,l.codeTagCount=o.length>=8?parseInt(o[8],10):null,l.fittingRules=function(s,r){let n,a,o,c,l={},p=[[16384,"vLayout",i],[8192,"vLayout",t],[4096,"vRule5",!0],[2048,"vRule4",!0],[1024,"vRule3",!0],[512,"vRule2",!0],[256,"vRule1",!0],[128,"hLayout",i],[64,"hLayout",t],[32,"hRule6",!0],[16,"hRule5",!0],[8,"hRule4",!0],[4,"hRule3",!0],[2,"hRule2",!0],[1,"hRule1",!0]];for(n=null!==r?r:s,a=0,o=p.length;a=c[0]?(n-=c[0],l[c[1]]=void 0===l[c[1]]?c[2]:l[c[1]]):"vLayout"!==c[1]&&"hLayout"!==c[1]&&(l[c[1]]=!1),a++;return void 0===l.hLayout?0===s?l.hLayout=t:-1===s?l.hLayout=e:l.hRule1||l.hRule2||l.hRule3||l.hRule4||l.hRule5||l.hRule6?l.hLayout=3:l.hLayout=i:l.hLayout===i&&(l.hRule1||l.hRule2||l.hRule3||l.hRule4||l.hRule5||l.hRule6)&&(l.hLayout=3),void 0===l.vLayout?l.vRule1||l.vRule2||l.vRule3||l.vRule4||l.vRule5?l.vLayout=3:l.vLayout=e:l.vLayout===i&&(l.vRule1||l.vRule2||l.vRule3||l.vRule4||l.vRule5)&&(l.vLayout=3),l}(l.oldLayout,l.fullLayout),c.options=l,1!==l.hardBlank.length||isNaN(l.height)||isNaN(l.baseline)||isNaN(l.maxLength)||isNaN(l.oldLayout)||isNaN(l.numCommentLines))throw new Error("FIGlet header contains invalid values.");let p,A=[];for(p=32;p<=126;p++)A.push(p);if(A=A.concat(196,214,220,228,246,252,223),a.length0&&c.numChars0;){if(u=a.splice(0,1)[0].split(" ")[0],/^0[xX][0-9a-fA-F]+$/.test(u))u=parseInt(u,16);else if(/^0[0-7]+$/.test(u))u=parseInt(u,8);else if(/^[0-9]+$/.test(u))u=parseInt(u,10);else{if(!/^-0[xX][0-9a-fA-F]+$/.test(u)){if(""===u)break;console.log("Invalid data:"+u),h=!0;break}u=parseInt(u,16)}for(c[u]=a.splice(0,l.height),p=0;pe.text())).then((function(e){i.push(e)}))}))}),Promise.resolve()).then((function(s){for(var r in e)e.hasOwnProperty(r)&&S.parseFont(e[r],i[r]);t&&t()}))},S.figFonts=s,S})();void 0!==e.exports&&(e.exports=t)},47707:(e,t,i)=>{const s=i(88042),r=i(57147),n=i(71017),a=n.join(__dirname,"/../fonts/");s.loadFont=function(e,t){s.figFonts[e]?t(null,s.figFonts[e].options):r.readFile(n.join(a,e+".flf"),{encoding:"utf-8"},(function(i,r){if(i)return t(i);r+="";try{t(null,s.parseFont(e,r))}catch(e){t(e)}}))},s.loadFontSync=function(e){if(s.figFonts[e])return s.figFonts[e].options;var t=r.readFileSync(n.join(a,e+".flf"),{encoding:"utf-8"});return t+="",s.parseFont(e,t)},s.fonts=function(e){var t=[];r.readdir(a,(function(i,s){if(i)return e(i);s.forEach((function(e){/\.flf$/.test(e)&&t.push(e.replace(/\.flf$/,""))})),e(null,t)}))},s.fontsSync=function(){var e=[];return r.readdirSync(a).forEach((function(t){/\.flf$/.test(t)&&e.push(t.replace(/\.flf$/,""))})),e},e.exports=s},12937:(e,t,i)=>{var s;e.exports=function(){if(!s){try{s=i(8856)("follow-redirects")}catch(e){}"function"!=typeof s&&(s=function(){})}s.apply(null,arguments)}},12564:(e,t,i)=>{var s=i(57310),r=s.URL,n=i(13685),a=i(95687),o=i(12781).Writable,c=i(39491),l=i(12937),p=["abort","aborted","connect","error","socket","timeout"],A=Object.create(null);p.forEach((function(e){A[e]=function(t,i,s){this._redirectable.emit(e,t,i,s)}}));var u=v("ERR_FR_REDIRECTION_FAILURE","Redirected request failed"),d=v("ERR_FR_TOO_MANY_REDIRECTS","Maximum number of redirects exceeded"),h=v("ERR_FR_MAX_BODY_LENGTH_EXCEEDED","Request body larger than maxBodyLength limit"),m=v("ERR_STREAM_WRITE_AFTER_END","write after end");function g(e,t){o.call(this),this._sanitizeOptions(e),this._options=e,this._ended=!1,this._ending=!1,this._redirectCount=0,this._redirects=[],this._requestBodyLength=0,this._requestBodyBuffers=[],t&&this.on("response",t);var i=this;this._onNativeResponse=function(e){i._processResponse(e)},this._performRequest()}function f(e){var t={maxRedirects:21,maxBodyLength:10485760},i={};return Object.keys(e).forEach((function(n){var a=n+":",o=i[a]=e[n],p=t[n]=Object.create(o);Object.defineProperties(p,{request:{value:function(e,n,o){if("string"==typeof e){var p=e;try{e=C(new r(p))}catch(t){e=s.parse(p)}}else r&&e instanceof r?e=C(e):(o=n,n=e,e={protocol:a});return"function"==typeof n&&(o=n,n=null),(n=Object.assign({maxRedirects:t.maxRedirects,maxBodyLength:t.maxBodyLength},e,n)).nativeProtocols=i,c.equal(n.protocol,a,"protocol mismatch"),l("options",n),new g(n,o)},configurable:!0,enumerable:!0,writable:!0},get:{value:function(e,t,i){var s=p.request(e,t,i);return s.end(),s},configurable:!0,enumerable:!0,writable:!0}})})),t}function E(){}function C(e){var t={protocol:e.protocol,hostname:e.hostname.startsWith("[")?e.hostname.slice(1,-1):e.hostname,hash:e.hash,search:e.search,pathname:e.pathname,path:e.pathname+e.search,href:e.href};return""!==e.port&&(t.port=Number(e.port)),t}function y(e,t){var i;for(var s in t)e.test(s)&&(i=t[s],delete t[s]);return null==i?void 0:String(i).trim()}function v(e,t){function i(e){Error.captureStackTrace(this,this.constructor),e?(this.message=t+": "+e.message,this.cause=e):this.message=t}return i.prototype=new Error,i.prototype.constructor=i,i.prototype.name="Error ["+e+"]",i.prototype.code=e,i}function w(e){for(var t of p)e.removeListener(t,A[t]);e.on("error",E),e.abort()}g.prototype=Object.create(o.prototype),g.prototype.abort=function(){w(this._currentRequest),this.emit("abort")},g.prototype.write=function(e,t,i){if(this._ending)throw new m;if(!("string"==typeof e||"object"==typeof e&&"length"in e))throw new TypeError("data should be a string, Buffer or Uint8Array");"function"==typeof t&&(i=t,t=null),0!==e.length?this._requestBodyLength+e.length<=this._options.maxBodyLength?(this._requestBodyLength+=e.length,this._requestBodyBuffers.push({data:e,encoding:t}),this._currentRequest.write(e,t,i)):(this.emit("error",new h),this.abort()):i&&i()},g.prototype.end=function(e,t,i){if("function"==typeof e?(i=e,e=t=null):"function"==typeof t&&(i=t,t=null),e){var s=this,r=this._currentRequest;this.write(e,t,(function(){s._ended=!0,r.end(null,null,i)})),this._ending=!0}else this._ended=this._ending=!0,this._currentRequest.end(null,null,i)},g.prototype.setHeader=function(e,t){this._options.headers[e]=t,this._currentRequest.setHeader(e,t)},g.prototype.removeHeader=function(e){delete this._options.headers[e],this._currentRequest.removeHeader(e)},g.prototype.setTimeout=function(e,t){var i=this;function s(t){t.setTimeout(e),t.removeListener("timeout",t.destroy),t.addListener("timeout",t.destroy)}function r(t){i._timeout&&clearTimeout(i._timeout),i._timeout=setTimeout((function(){i.emit("timeout"),n()}),e),s(t)}function n(){i._timeout&&(clearTimeout(i._timeout),i._timeout=null),i.removeListener("abort",n),i.removeListener("error",n),i.removeListener("response",n),t&&i.removeListener("timeout",t),i.socket||i._currentRequest.removeListener("socket",r)}return t&&this.on("timeout",t),this.socket?r(this.socket):this._currentRequest.once("socket",r),this.on("socket",s),this.on("abort",n),this.on("error",n),this.on("response",n),this},["flushHeaders","getHeader","setNoDelay","setSocketKeepAlive"].forEach((function(e){g.prototype[e]=function(t,i){return this._currentRequest[e](t,i)}})),["aborted","connection","socket"].forEach((function(e){Object.defineProperty(g.prototype,e,{get:function(){return this._currentRequest[e]}})})),g.prototype._sanitizeOptions=function(e){if(e.headers||(e.headers={}),e.host&&(e.hostname||(e.hostname=e.host),delete e.host),!e.pathname&&e.path){var t=e.path.indexOf("?");t<0?e.pathname=e.path:(e.pathname=e.path.substring(0,t),e.search=e.path.substring(t))}},g.prototype._performRequest=function(){var e=this._options.protocol,t=this._options.nativeProtocols[e];if(t){if(this._options.agents){var i=e.slice(0,-1);this._options.agent=this._options.agents[i]}var r=this._currentRequest=t.request(this._options,this._onNativeResponse);for(var n of(r._redirectable=this,p))r.on(n,A[n]);if(this._currentUrl=/^\//.test(this._options.path)?s.format(this._options):this._currentUrl=this._options.path,this._isRedirect){var a=0,o=this,c=this._requestBodyBuffers;!function e(t){if(r===o._currentRequest)if(t)o.emit("error",t);else if(a=400)return e.responseUrl=this._currentUrl,e.redirects=this._redirects,this.emit("response",e),void(this._requestBodyBuffers=[]);if(w(this._currentRequest),e.destroy(),++this._redirectCount>this._options.maxRedirects)this.emit("error",new d);else{var r,n=this._options.beforeRedirect;n&&(r=Object.assign({Host:e.req.getHeader("host")},this._options.headers));var a=this._options.method;((301===t||302===t)&&"POST"===this._options.method||303===t&&!/^(?:GET|HEAD)$/.test(this._options.method))&&(this._options.method="GET",this._requestBodyBuffers=[],y(/^content-/i,this._options.headers));var o,c=y(/^host$/i,this._options.headers),p=s.parse(this._currentUrl),A=c||p.host,h=/^\w+:/.test(i)?this._currentUrl:s.format(Object.assign(p,{host:A}));try{o=s.resolve(h,i)}catch(e){return void this.emit("error",new u(e))}l("redirecting to",o),this._isRedirect=!0;var m=s.parse(o);if(Object.assign(this._options,m),(m.protocol!==p.protocol&&"https:"!==m.protocol||m.host!==A&&!function(e,t){const i=e.length-t.length-1;return i>0&&"."===e[i]&&e.endsWith(t)}(m.host,A))&&y(/^(?:authorization|cookie)$/i,this._options.headers),"function"==typeof n){var g={headers:e.headers,statusCode:t},f={url:h,method:a,headers:r};try{n(this._options,g,f)}catch(e){return void this.emit("error",e)}this._sanitizeOptions(this._options)}try{this._performRequest()}catch(e){this.emit("error",new u(e))}}},e.exports=f({http:n,https:a}),e.exports.wrap=f},90504:(e,t,i)=>{var s=i(14598),r=i(73837),n=i(71017),a=i(13685),o=i(95687),c=i(57310).parse,l=i(57147),p=i(69335),A=i(62720),u=i(91117);function d(e){if(!(this instanceof d))return new d;for(var t in this._overheadLength=0,this._valueLength=0,this._valuesToMeasure=[],s.call(this),e=e||{})this[t]=e[t]}e.exports=d,r.inherits(d,s),d.LINE_BREAK="\r\n",d.DEFAULT_CONTENT_TYPE="application/octet-stream",d.prototype.append=function(e,t,i){"string"==typeof(i=i||{})&&(i={filename:i});var n=s.prototype.append.bind(this);if("number"==typeof t&&(t=""+t),r.isArray(t))this._error(new Error("Arrays are not supported."));else{var a=this._multiPartHeader(e,t,i),o=this._multiPartFooter();n(a),n(t),n(o),this._trackLength(a,t,i)}},d.prototype._trackLength=function(e,t,i){var s=0;null!=i.knownLength?s+=+i.knownLength:Buffer.isBuffer(t)?s=t.length:"string"==typeof t&&(s=Buffer.byteLength(t)),this._valueLength+=s,this._overheadLength+=Buffer.byteLength(e)+d.LINE_BREAK.length,t&&(t.path||t.readable&&t.hasOwnProperty("httpVersion"))&&(i.knownLength||this._valuesToMeasure.push(t))},d.prototype._lengthRetriever=function(e,t){e.hasOwnProperty("fd")?null!=e.end&&e.end!=1/0&&null!=e.start?t(null,e.end+1-(e.start?e.start:0)):l.stat(e.path,(function(i,s){var r;i?t(i):(r=s.size-(e.start?e.start:0),t(null,r))})):e.hasOwnProperty("httpVersion")?t(null,+e.headers["content-length"]):e.hasOwnProperty("httpModule")?(e.on("response",(function(i){e.pause(),t(null,+i.headers["content-length"])})),e.resume()):t("Unknown stream")},d.prototype._multiPartHeader=function(e,t,i){if("string"==typeof i.header)return i.header;var s,r=this._getContentDisposition(t,i),n=this._getContentType(t,i),a="",o={"Content-Disposition":["form-data",'name="'+e+'"'].concat(r||[]),"Content-Type":[].concat(n||[])};for(var c in"object"==typeof i.header&&u(o,i.header),o)o.hasOwnProperty(c)&&null!=(s=o[c])&&(Array.isArray(s)||(s=[s]),s.length&&(a+=c+": "+s.join("; ")+d.LINE_BREAK));return"--"+this.getBoundary()+d.LINE_BREAK+a+d.LINE_BREAK},d.prototype._getContentDisposition=function(e,t){var i,s;return"string"==typeof t.filepath?i=n.normalize(t.filepath).replace(/\\/g,"/"):t.filename||e.name||e.path?i=n.basename(t.filename||e.name||e.path):e.readable&&e.hasOwnProperty("httpVersion")&&(i=n.basename(e.client._httpMessage.path||"")),i&&(s='filename="'+i+'"'),s},d.prototype._getContentType=function(e,t){var i=t.contentType;return!i&&e.name&&(i=p.lookup(e.name)),!i&&e.path&&(i=p.lookup(e.path)),!i&&e.readable&&e.hasOwnProperty("httpVersion")&&(i=e.headers["content-type"]),i||!t.filepath&&!t.filename||(i=p.lookup(t.filepath||t.filename)),i||"object"!=typeof e||(i=d.DEFAULT_CONTENT_TYPE),i},d.prototype._multiPartFooter=function(){return function(e){var t=d.LINE_BREAK;0===this._streams.length&&(t+=this._lastBoundary()),e(t)}.bind(this)},d.prototype._lastBoundary=function(){return"--"+this.getBoundary()+"--"+d.LINE_BREAK},d.prototype.getHeaders=function(e){var t,i={"content-type":"multipart/form-data; boundary="+this.getBoundary()};for(t in e)e.hasOwnProperty(t)&&(i[t.toLowerCase()]=e[t]);return i},d.prototype.getBoundary=function(){return this._boundary||this._generateBoundary(),this._boundary},d.prototype.getBuffer=function(){for(var e=new Buffer.alloc(0),t=this.getBoundary(),i=0,s=this._streams.length;i{e.exports=function(e,t){return Object.keys(t).forEach((function(i){e[i]=e[i]||t[i]})),e}},87534:(e,t,i)=>{var s=i(14598),r=i(73837),n=i(71017),a=i(13685),o=i(95687),c=i(57310).parse,l=i(57147),p=i(12781).Stream,A=i(69335),u=i(62720),d=i(39049);function h(e){if(!(this instanceof h))return new h(e);for(var t in this._overheadLength=0,this._valueLength=0,this._valuesToMeasure=[],s.call(this),e=e||{})this[t]=e[t]}e.exports=h,r.inherits(h,s),h.LINE_BREAK="\r\n",h.DEFAULT_CONTENT_TYPE="application/octet-stream",h.prototype.append=function(e,t,i){"string"==typeof(i=i||{})&&(i={filename:i});var n=s.prototype.append.bind(this);if("number"==typeof t&&(t=""+t),r.isArray(t))this._error(new Error("Arrays are not supported."));else{var a=this._multiPartHeader(e,t,i),o=this._multiPartFooter();n(a),n(t),n(o),this._trackLength(a,t,i)}},h.prototype._trackLength=function(e,t,i){var s=0;null!=i.knownLength?s+=+i.knownLength:Buffer.isBuffer(t)?s=t.length:"string"==typeof t&&(s=Buffer.byteLength(t)),this._valueLength+=s,this._overheadLength+=Buffer.byteLength(e)+h.LINE_BREAK.length,t&&(t.path||t.readable&&t.hasOwnProperty("httpVersion")||t instanceof p)&&(i.knownLength||this._valuesToMeasure.push(t))},h.prototype._lengthRetriever=function(e,t){e.hasOwnProperty("fd")?null!=e.end&&e.end!=1/0&&null!=e.start?t(null,e.end+1-(e.start?e.start:0)):l.stat(e.path,(function(i,s){var r;i?t(i):(r=s.size-(e.start?e.start:0),t(null,r))})):e.hasOwnProperty("httpVersion")?t(null,+e.headers["content-length"]):e.hasOwnProperty("httpModule")?(e.on("response",(function(i){e.pause(),t(null,+i.headers["content-length"])})),e.resume()):t("Unknown stream")},h.prototype._multiPartHeader=function(e,t,i){if("string"==typeof i.header)return i.header;var s,r=this._getContentDisposition(t,i),n=this._getContentType(t,i),a="",o={"Content-Disposition":["form-data",'name="'+e+'"'].concat(r||[]),"Content-Type":[].concat(n||[])};for(var c in"object"==typeof i.header&&d(o,i.header),o)o.hasOwnProperty(c)&&null!=(s=o[c])&&(Array.isArray(s)||(s=[s]),s.length&&(a+=c+": "+s.join("; ")+h.LINE_BREAK));return"--"+this.getBoundary()+h.LINE_BREAK+a+h.LINE_BREAK},h.prototype._getContentDisposition=function(e,t){var i,s;return"string"==typeof t.filepath?i=n.normalize(t.filepath).replace(/\\/g,"/"):t.filename||e.name||e.path?i=n.basename(t.filename||e.name||e.path):e.readable&&e.hasOwnProperty("httpVersion")&&(i=n.basename(e.client._httpMessage.path||"")),i&&(s='filename="'+i+'"'),s},h.prototype._getContentType=function(e,t){var i=t.contentType;return!i&&e.name&&(i=A.lookup(e.name)),!i&&e.path&&(i=A.lookup(e.path)),!i&&e.readable&&e.hasOwnProperty("httpVersion")&&(i=e.headers["content-type"]),i||!t.filepath&&!t.filename||(i=A.lookup(t.filepath||t.filename)),i||"object"!=typeof e||(i=h.DEFAULT_CONTENT_TYPE),i},h.prototype._multiPartFooter=function(){return function(e){var t=h.LINE_BREAK;0===this._streams.length&&(t+=this._lastBoundary()),e(t)}.bind(this)},h.prototype._lastBoundary=function(){return"--"+this.getBoundary()+"--"+h.LINE_BREAK},h.prototype.getHeaders=function(e){var t,i={"content-type":"multipart/form-data; boundary="+this.getBoundary()};for(t in e)e.hasOwnProperty(t)&&(i[t.toLowerCase()]=e[t]);return i},h.prototype.setBoundary=function(e){this._boundary=e},h.prototype.getBoundary=function(){return this._boundary||this._generateBoundary(),this._boundary},h.prototype.getBuffer=function(){for(var e=new Buffer.alloc(0),t=this.getBoundary(),i=0,s=this._streams.length;i{e.exports=function(e,t){return Object.keys(t).forEach((function(i){e[i]=e[i]||t[i]})),e}},94062:e=>{e.exports=function(e){return[...e].reduce(((e,[t,i])=>(e[t]=i,e)),{})}},38125:(e,t,i)=>{"use strict";const s=i(57147),r=i(66577),n=i(62683),a=i(97537),o=i(56398),c=i(2237),l=i(68042),p=i(28418),A=i(38164);function u(e,t){if(""===e)return{data:{},content:e,excerpt:"",orig:e};let i=l(e);const s=u.cache[i.content];if(!t){if(s)return i=Object.assign({},s),i.orig=s.orig,i;u.cache[i.content]=i}return function(e,t){const i=n(t),s=i.delimiters[0],a="\n"+i.delimiters[1];let c=e.content;i.language&&(e.language=i.language);const l=s.length;if(!A.startsWith(c,s,l))return o(e,i),e;if(c.charAt(l)===s.slice(-1))return e;c=c.slice(l);const d=c.length,h=u.language(c,i);h.name&&(e.language=h.name,c=c.slice(h.raw.length));let m=c.indexOf(a);-1===m&&(m=d),e.matter=c.slice(0,m);return""===e.matter.replace(/^\s*#[^\n]+/gm,"").trim()?(e.isEmpty=!0,e.empty=e.content,e.data={}):e.data=p(e.language,e.matter,i),m===d?e.content="":(e.content=c.slice(m+a.length),"\r"===e.content[0]&&(e.content=e.content.slice(1)),"\n"===e.content[0]&&(e.content=e.content.slice(1))),o(e,i),(!0===i.sections||"function"==typeof i.section)&&r(e,i.section),e}(i,t)}u.engines=c,u.stringify=function(e,t,i){return"string"==typeof e&&(e=u(e,i)),a(e,t,i)},u.read=function(e,t){const i=u(s.readFileSync(e,"utf8"),t);return i.path=e,i},u.test=function(e,t){return A.startsWith(e,n(t).delimiters[0])},u.language=function(e,t){const i=n(t).delimiters[0];u.test(e)&&(e=e.slice(i.length));const s=e.slice(0,e.search(/\r?\n/));return{raw:s,name:s?s.trim():""}},u.cache={},u.clearCache=function(){u.cache={}},e.exports=u},62683:(e,t,i)=>{"use strict";const s=i(2237),r=i(38164);e.exports=function(e){const t=Object.assign({},e);return t.delimiters=r.arrayify(t.delims||t.delimiters||"---"),1===t.delimiters.length&&t.delimiters.push(t.delimiters[0]),t.language=(t.language||t.lang||"yaml").toLowerCase(),t.engines=Object.assign({},s,t.parsers,t.engines),t}},21825:e=>{"use strict";e.exports=function(e,t){let i=t.engines[e]||t.engines[function(e){switch(e.toLowerCase()){case"js":case"javascript":return"javascript";case"coffee":case"coffeescript":case"cson":return"coffee";case"yaml":case"yml":return"yaml";default:return e}}(e)];if(void 0===i)throw new Error('gray-matter engine "'+e+'" is not registered');return"function"==typeof i&&(i={parse:i}),i}},2237:(module,exports,__webpack_require__)=>{"use strict";const yaml=__webpack_require__(43236),engines=exports=module.exports;engines.yaml={parse:yaml.safeLoad.bind(yaml),stringify:yaml.safeDump.bind(yaml)},engines.json={parse:JSON.parse.bind(JSON),stringify:function(e,t){const i=Object.assign({replacer:null,space:2},t);return JSON.stringify(e,i.replacer,i.space)}},engines.javascript={parse:function parse(str,options,wrap){try{return!1!==wrap&&(str="(function() {\nreturn "+str.trim()+";\n}());"),eval(str)||{}}catch(e){if(!1!==wrap&&/(unexpected|identifier)/i.test(e.message))return parse(str,options,!1);throw new SyntaxError(e)}},stringify:function(){throw new Error("stringifying JavaScript is not supported")}}},56398:(e,t,i)=>{"use strict";const s=i(62683);e.exports=function(e,t){const i=s(t);if(null==e.data&&(e.data={}),"function"==typeof i.excerpt)return i.excerpt(e,i);const r=e.data.excerpt_separator||i.excerpt_separator;if(null==r&&(!1===i.excerpt||null==i.excerpt))return e;const n="string"==typeof i.excerpt?i.excerpt:r||i.delimiters[0],a=e.content.indexOf(n);return-1!==a&&(e.excerpt=e.content.slice(0,a)),e}},28418:(e,t,i)=>{"use strict";const s=i(21825),r=i(62683);e.exports=function(e,t,i){const n=r(i),a=s(e,n);if("function"!=typeof a.parse)throw new TypeError('expected "'+e+'.parse" to be a function');return a.parse(t,n)}},97537:(e,t,i)=>{"use strict";const s=i(79049),r=i(21825),n=i(62683);function a(e){return"\n"!==e.slice(-1)?e+"\n":e}e.exports=function(e,t,i){if(null==t&&null==i)switch(s(e)){case"object":t=e.data,i={};break;case"string":return e;default:throw new TypeError("expected file to be a string or object")}const o=e.content,c=n(i);if(null==t){if(!c.data)return e;t=c.data}const l=e.language||c.language,p=r(l,c);if("function"!=typeof p.stringify)throw new TypeError('expected "'+l+'.stringify" to be a function');t=Object.assign({},e.data,t);const A=c.delimiters[0],u=c.delimiters[1],d=p.stringify(t,i).trim();let h="";return"{}"!==d&&(h=a(A)+a(d)+a(u)),"string"==typeof e.excerpt&&""!==e.excerpt&&-1===o.indexOf(e.excerpt.trim())&&(h+=a(e.excerpt)+a(u)),h+a(o)}},68042:(e,t,i)=>{"use strict";const s=i(79049),r=i(97537),n=i(38164);e.exports=function(e){return"object"!==s(e)&&(e={content:e}),"object"!==s(e.data)&&(e.data={}),e.contents&&null==e.content&&(e.content=e.contents),n.define(e,"orig",n.toBuffer(e.content)),n.define(e,"language",e.language||""),n.define(e,"matter",e.matter||""),n.define(e,"stringify",(function(t,i){return i&&i.language&&(e.language=i.language),r(e,t,i)})),e.content=n.toString(e.content),e.isEmpty=!1,e.excerpt="",e}},38164:(e,t,i)=>{"use strict";const s=i(47494),r=i(79049);t.define=function(e,t,i){Reflect.defineProperty(e,t,{enumerable:!1,configurable:!0,writable:!0,value:i})},t.isBuffer=function(e){return"buffer"===r(e)},t.isObject=function(e){return"object"===r(e)},t.toBuffer=function(e){return"string"==typeof e?Buffer.from(e):e},t.toString=function(e){if(t.isBuffer(e))return s(String(e));if("string"!=typeof e)throw new TypeError("expected input to be a string or buffer");return s(e)},t.arrayify=function(e){return e?Array.isArray(e)?e:[e]:[]},t.startsWith=function(e,t,i){return"number"!=typeof i&&(i=t.length),e.slice(0,i)===t}},5506:e=>{"use strict";e.exports=(e,t=process.argv)=>{const i=e.startsWith("-")?"":1===e.length?"-":"--",s=t.indexOf(i+e),r=t.indexOf("--");return-1!==s&&(-1===r||s{"use strict";e.exports=(e,t=1,i)=>{if(i={indent:" ",includeEmptyLines:!1,...i},"string"!=typeof e)throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof e}\``);if("number"!=typeof t)throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof t}\``);if("string"!=typeof i.indent)throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof i.indent}\``);if(0===t)return e;const s=i.includeEmptyLines?/^/gm:/^(?!\s*$)/gm;return e.replace(s,i.indent.repeat(t))}},44236:(e,t,i)=>{try{var s=i(73837);if("function"!=typeof s.inherits)throw"";e.exports=s.inherits}catch(t){e.exports=i(67483)}},67483:e=>{"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){if(t){e.super_=t;var i=function(){};i.prototype=t.prototype,e.prototype=new i,e.prototype.constructor=e}}},30547:e=>{e.exports=function(){return"undefined"!=typeof window&&"object"==typeof window.process&&"renderer"===window.process.type||!("undefined"==typeof process||"object"!=typeof process.versions||!process.versions.electron)||"object"==typeof navigator&&"string"==typeof navigator.userAgent&&navigator.userAgent.indexOf("Electron")>=0}},60195:e=>{"use strict";e.exports=function(e){return null!=e&&("object"==typeof e||"function"==typeof e)}},27759:e=>{"use strict";e.exports=({stream:e=process.stdout}={})=>Boolean(e&&e.isTTY&&"dumb"!==process.env.TERM&&!("CI"in process.env))},42412:e=>{"use strict";var t=e.exports=function(e){return null!==e&&"object"==typeof e&&"function"==typeof e.pipe};t.writable=function(e){return t(e)&&!1!==e.writable&&"function"==typeof e._write&&"object"==typeof e._writableState},t.readable=function(e){return t(e)&&!1!==e.readable&&"function"==typeof e._read&&"object"==typeof e._readableState},t.duplex=function(e){return t.writable(e)&&t.readable(e)},t.transform=function(e){return t.duplex(e)&&"function"==typeof e._transform&&"object"==typeof e._transformState}},5881:e=>{"use strict";e.exports=()=>"win32"!==process.platform||Boolean(process.env.CI)||Boolean(process.env.WT_SESSION)||"vscode"===process.env.TERM_PROGRAM||"xterm-256color"===process.env.TERM||"alacritty"===process.env.TERM},43236:(e,t,i)=>{"use strict";var s=i(5033);e.exports=s},5033:(e,t,i)=>{"use strict";var s=i(84780),r=i(44418);function n(e){return function(){throw new Error("Function "+e+" is deprecated and cannot be used.")}}e.exports.Type=i(42257),e.exports.Schema=i(38107),e.exports.FAILSAFE_SCHEMA=i(19777),e.exports.JSON_SCHEMA=i(93886),e.exports.CORE_SCHEMA=i(71514),e.exports.DEFAULT_SAFE_SCHEMA=i(79251),e.exports.DEFAULT_FULL_SCHEMA=i(17386),e.exports.load=s.load,e.exports.loadAll=s.loadAll,e.exports.safeLoad=s.safeLoad,e.exports.safeLoadAll=s.safeLoadAll,e.exports.dump=r.dump,e.exports.safeDump=r.safeDump,e.exports.YAMLException=i(86822),e.exports.MINIMAL_SCHEMA=i(19777),e.exports.SAFE_SCHEMA=i(79251),e.exports.DEFAULT_SCHEMA=i(17386),e.exports.scan=n("scan"),e.exports.parse=n("parse"),e.exports.compose=n("compose"),e.exports.addConstructor=n("addConstructor")},90560:e=>{"use strict";function t(e){return null==e}e.exports.isNothing=t,e.exports.isObject=function(e){return"object"==typeof e&&null!==e},e.exports.toArray=function(e){return Array.isArray(e)?e:t(e)?[]:[e]},e.exports.repeat=function(e,t){var i,s="";for(i=0;i{"use strict";var s=i(90560),r=i(86822),n=i(17386),a=i(79251),o=Object.prototype.toString,c=Object.prototype.hasOwnProperty,l=9,p=10,A=13,u=32,d=33,h=34,m=35,g=37,f=38,E=39,C=42,y=44,v=45,w=58,I=61,B=62,b=63,Q=64,x=91,k=93,D=96,S=123,_=124,R=125,T={0:"\\0",7:"\\a",8:"\\b",9:"\\t",10:"\\n",11:"\\v",12:"\\f",13:"\\r",27:"\\e",34:'\\"',92:"\\\\",133:"\\N",160:"\\_",8232:"\\L",8233:"\\P"},F=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"];function N(e){var t,i,n;if(t=e.toString(16).toUpperCase(),e<=255)i="x",n=2;else if(e<=65535)i="u",n=4;else{if(!(e<=4294967295))throw new r("code point within a string may not be greater than 0xFFFFFFFF");i="U",n=8}return"\\"+i+s.repeat("0",n-t.length)+t}function L(e){this.schema=e.schema||n,this.indent=Math.max(1,e.indent||2),this.noArrayIndent=e.noArrayIndent||!1,this.skipInvalid=e.skipInvalid||!1,this.flowLevel=s.isNothing(e.flowLevel)?-1:e.flowLevel,this.styleMap=function(e,t){var i,s,r,n,a,o,l;if(null===t)return{};for(i={},r=0,n=(s=Object.keys(t)).length;r-1&&i>=e.flowLevel;switch(function(e,t,i,s,r){var n,a,o,c,l=!1,A=!1,u=-1!==s,T=-1,F=P(c=e.charCodeAt(0))&&65279!==c&&!U(c)&&c!==v&&c!==b&&c!==w&&c!==y&&c!==x&&c!==k&&c!==S&&c!==R&&c!==m&&c!==f&&c!==C&&c!==d&&c!==_&&c!==I&&c!==B&&c!==E&&c!==h&&c!==g&&c!==Q&&c!==D&&!U(e.charCodeAt(e.length-1));if(t)for(n=0;n0?e.charCodeAt(n-1):null,F=F&&G(a,o)}else{for(n=0;ns&&" "!==e[T+1],T=n);else if(!P(a))return Y;o=n>0?e.charCodeAt(n-1):null,F=F&&G(a,o)}A=A||u&&n-T-1>s&&" "!==e[T+1]}return l||A?i>9&&j(e)?Y:A?q:H:F&&!r(e)?V:J}(t,o,e.indent,a,(function(t){return function(e,t){var i,s;for(i=0,s=e.implicitTypes.length;i"+z(t,e.indent)+$(O(function(e,t){for(var i,s,r,n=/(\n+)([^\n]*)/g,a=(r=-1!==(r=e.indexOf("\n"))?r:e.length,n.lastIndex=r,X(e.slice(0,r),t)),o="\n"===e[0]||" "===e[0];s=n.exec(e);){var c=s[1],l=s[2];i=" "===l[0],a+=c+(o||i||""===l?"":"\n")+X(l,t),o=i}return a}(t,a),n));case Y:return'"'+function(e){for(var t,i,s,r="",n=0;n=55296&&t<=56319&&(i=e.charCodeAt(n+1))>=56320&&i<=57343?(r+=N(1024*(t-55296)+i-56320+65536),n++):r+=!(s=T[t])&&P(t)?e[n]:s||N(t);return r}(t)+'"';default:throw new r("impossible error: invalid scalar style")}}()}function z(e,t){var i=j(e)?String(t):"",s="\n"===e[e.length-1];return i+(!s||"\n"!==e[e.length-2]&&"\n"!==e?s?"":"-":"+")+"\n"}function $(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function X(e,t){if(""===e||" "===e[0])return e;for(var i,s,r=/ [^ ]/g,n=0,a=0,o=0,c="";i=r.exec(e);)(o=i.index)-n>t&&(s=a>n?a:o,c+="\n"+e.slice(n,s),n=s+1),a=o;return c+="\n",e.length-n>t&&a>n?c+=e.slice(n,a)+"\n"+e.slice(a+1):c+=e.slice(n),c.slice(1)}function K(e,t,i){var s,n,a,l,p,A;for(a=0,l=(n=i?e.explicitTypes:e.implicitTypes).length;a tag resolver accepts not "'+A+'" style');s=p.represent[A](t,A)}e.dump=s}return!0}return!1}function Z(e,t,i,s,n,a){e.tag=null,e.dump=i,K(e,i,!1)||K(e,i,!0);var c=o.call(e.dump);s&&(s=e.flowLevel<0||e.flowLevel>t);var l,A,u="[object Object]"===c||"[object Array]"===c;if(u&&(A=-1!==(l=e.duplicates.indexOf(i))),(null!==e.tag&&"?"!==e.tag||A||2!==e.indent&&t>0)&&(n=!1),A&&e.usedDuplicates[l])e.dump="*ref_"+l;else{if(u&&A&&!e.usedDuplicates[l]&&(e.usedDuplicates[l]=!0),"[object Object]"===c)s&&0!==Object.keys(e.dump).length?(function(e,t,i,s){var n,a,o,c,l,A,u="",d=e.tag,h=Object.keys(i);if(!0===e.sortKeys)h.sort();else if("function"==typeof e.sortKeys)h.sort(e.sortKeys);else if(e.sortKeys)throw new r("sortKeys must be a boolean or a function");for(n=0,a=h.length;n1024)&&(e.dump&&p===e.dump.charCodeAt(0)?A+="?":A+="? "),A+=e.dump,l&&(A+=M(e,t)),Z(e,t+1,c,!0,l)&&(e.dump&&p===e.dump.charCodeAt(0)?A+=":":A+=": ",u+=A+=e.dump));e.tag=d,e.dump=u||"{}"}(e,t,e.dump,n),A&&(e.dump="&ref_"+l+e.dump)):(function(e,t,i){var s,r,n,a,o,c="",l=e.tag,p=Object.keys(i);for(s=0,r=p.length;s1024&&(o+="? "),o+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),Z(e,t,a,!1,!1)&&(c+=o+=e.dump));e.tag=l,e.dump="{"+c+"}"}(e,t,e.dump),A&&(e.dump="&ref_"+l+" "+e.dump));else if("[object Array]"===c){var d=e.noArrayIndent&&t>0?t-1:t;s&&0!==e.dump.length?(function(e,t,i,s){var r,n,a="",o=e.tag;for(r=0,n=i.length;r "+e.dump)}return!0}function ee(e,t){var i,s,r=[],n=[];for(te(e,r,n),i=0,s=n.length;i{"use strict";function t(e,t){Error.call(this),this.name="YAMLException",this.reason=e,this.mark=t,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||""}t.prototype=Object.create(Error.prototype),t.prototype.constructor=t,t.prototype.toString=function(e){var t=this.name+": ";return t+=this.reason||"(unknown reason)",!e&&this.mark&&(t+=" "+this.mark.toString()),t},e.exports=t},84780:(e,t,i)=>{"use strict";var s=i(90560),r=i(86822),n=i(20917),a=i(79251),o=i(17386),c=Object.prototype.hasOwnProperty,l=1,p=2,A=3,u=4,d=1,h=2,m=3,g=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,f=/[\x85\u2028\u2029]/,E=/[,\[\]\{\}]/,C=/^(?:!|!!|![a-z\-]+!)$/i,y=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function v(e){return Object.prototype.toString.call(e)}function w(e){return 10===e||13===e}function I(e){return 9===e||32===e}function B(e){return 9===e||32===e||10===e||13===e}function b(e){return 44===e||91===e||93===e||123===e||125===e}function Q(e){var t;return 48<=e&&e<=57?e-48:97<=(t=32|e)&&t<=102?t-97+10:-1}function x(e){return 48===e?"\0":97===e?"":98===e?"\b":116===e||9===e?"\t":110===e?"\n":118===e?"\v":102===e?"\f":114===e?"\r":101===e?"":32===e?" ":34===e?'"':47===e?"/":92===e?"\\":78===e?"…":95===e?" ":76===e?"\u2028":80===e?"\u2029":""}function k(e){return e<=65535?String.fromCharCode(e):String.fromCharCode(55296+(e-65536>>10),56320+(e-65536&1023))}for(var D=new Array(256),S=new Array(256),_=0;_<256;_++)D[_]=x(_)?1:0,S[_]=x(_);function R(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||o,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function T(e,t){return new r(t,new n(e.filename,e.input,e.position,e.line,e.position-e.lineStart))}function F(e,t){throw T(e,t)}function N(e,t){e.onWarning&&e.onWarning.call(null,T(e,t))}var L={YAML:function(e,t,i){var s,r,n;null!==e.version&&F(e,"duplication of %YAML directive"),1!==i.length&&F(e,"YAML directive accepts exactly one argument"),null===(s=/^([0-9]+)\.([0-9]+)$/.exec(i[0]))&&F(e,"ill-formed argument of the YAML directive"),r=parseInt(s[1],10),n=parseInt(s[2],10),1!==r&&F(e,"unacceptable YAML version of the document"),e.version=i[0],e.checkLineBreaks=n<2,1!==n&&2!==n&&N(e,"unsupported YAML version of the document")},TAG:function(e,t,i){var s,r;2!==i.length&&F(e,"TAG directive accepts exactly two arguments"),s=i[0],r=i[1],C.test(s)||F(e,"ill-formed tag handle (first argument) of the TAG directive"),c.call(e.tagMap,s)&&F(e,'there is a previously declared suffix for "'+s+'" tag handle'),y.test(r)||F(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[s]=r}};function O(e,t,i,s){var r,n,a,o;if(t1&&(e.result+=s.repeat("\n",t-1))}function J(e,t){var i,s,r=e.tag,n=e.anchor,a=[],o=!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=a),s=e.input.charCodeAt(e.position);0!==s&&45===s&&B(e.input.charCodeAt(e.position+1));)if(o=!0,e.position++,G(e,!0,-1)&&e.lineIndent<=t)a.push(null),s=e.input.charCodeAt(e.position);else if(i=e.line,Y(e,t,A,!1,!0),a.push(e.result),G(e,!0,-1),s=e.input.charCodeAt(e.position),(e.line===i||e.lineIndent>t)&&0!==s)F(e,"bad indentation of a sequence entry");else if(e.lineIndentt?x=1:e.lineIndent===t?x=0:e.lineIndentt?x=1:e.lineIndent===t?x=0:e.lineIndentt)&&(Y(e,t,u,!0,r)&&(f?m=e.result:g=e.result),f||(U(e,A,d,h,m,g,n,a),h=m=g=null),G(e,!0,-1),o=e.input.charCodeAt(e.position)),e.lineIndent>t&&0!==o)F(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===n?F(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):p?F(e,"repeat of an indentation width identifier"):(A=t+n-1,p=!0)}if(I(a)){do{a=e.input.charCodeAt(++e.position)}while(I(a));if(35===a)do{a=e.input.charCodeAt(++e.position)}while(!w(a)&&0!==a)}for(;0!==a;){for(P(e),e.lineIndent=0,a=e.input.charCodeAt(e.position);(!p||e.lineIndentA&&(A=e.lineIndent),w(a))u++;else{if(e.lineIndent0){for(r=a,n=0;r>0;r--)(a=Q(o=e.input.charCodeAt(++e.position)))>=0?n=(n<<4)+a:F(e,"expected hexadecimal character");e.result+=k(n),e.position++}else F(e,"unknown escape sequence");i=s=e.position}else w(o)?(O(e,i,s,!0),V(e,G(e,!1,t)),i=s=e.position):e.position===e.lineStart&&j(e)?F(e,"unexpected end of the document within a double quoted scalar"):(e.position++,s=e.position)}F(e,"unexpected end of the stream within a double quoted scalar")}(e,y)?R=!0:function(e){var t,i,s;if(42!==(s=e.input.charCodeAt(e.position)))return!1;for(s=e.input.charCodeAt(++e.position),t=e.position;0!==s&&!B(s)&&!b(s);)s=e.input.charCodeAt(++e.position);return e.position===t&&F(e,"name of an alias node must contain at least one character"),i=e.input.slice(t,e.position),c.call(e.anchorMap,i)||F(e,'unidentified alias "'+i+'"'),e.result=e.anchorMap[i],G(e,!0,-1),!0}(e)?(R=!0,null===e.tag&&null===e.anchor||F(e,"alias node should not have any properties")):function(e,t,i){var s,r,n,a,o,c,l,p,A=e.kind,u=e.result;if(B(p=e.input.charCodeAt(e.position))||b(p)||35===p||38===p||42===p||33===p||124===p||62===p||39===p||34===p||37===p||64===p||96===p)return!1;if((63===p||45===p)&&(B(s=e.input.charCodeAt(e.position+1))||i&&b(s)))return!1;for(e.kind="scalar",e.result="",r=n=e.position,a=!1;0!==p;){if(58===p){if(B(s=e.input.charCodeAt(e.position+1))||i&&b(s))break}else if(35===p){if(B(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&j(e)||i&&b(p))break;if(w(p)){if(o=e.line,c=e.lineStart,l=e.lineIndent,G(e,!1,-1),e.lineIndent>=t){a=!0,p=e.input.charCodeAt(e.position);continue}e.position=n,e.line=o,e.lineStart=c,e.lineIndent=l;break}}a&&(O(e,r,n,!1),V(e,e.line-o),r=n=e.position,a=!1),I(p)||(n=e.position+1),p=e.input.charCodeAt(++e.position)}return O(e,r,n,!1),!!e.result||(e.kind=A,e.result=u,!1)}(e,y,l===i)&&(R=!0,null===e.tag&&(e.tag="?")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===x&&(R=g&&J(e,v))),null!==e.tag&&"!"!==e.tag)if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&F(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),f=0,E=e.implicitTypes.length;f tag; it should be "'+C.kind+'", not "'+e.kind+'"'),C.resolve(e.result)?(e.result=C.construct(e.result),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):F(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")):F(e,"unknown tag !<"+e.tag+">");return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||R}function W(e){var t,i,s,r,n=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap={},e.anchorMap={};0!==(r=e.input.charCodeAt(e.position))&&(G(e,!0,-1),r=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==r));){for(a=!0,r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!B(r);)r=e.input.charCodeAt(++e.position);for(s=[],(i=e.input.slice(t,e.position)).length<1&&F(e,"directive name must not be less than one character in length");0!==r;){for(;I(r);)r=e.input.charCodeAt(++e.position);if(35===r){do{r=e.input.charCodeAt(++e.position)}while(0!==r&&!w(r));break}if(w(r))break;for(t=e.position;0!==r&&!B(r);)r=e.input.charCodeAt(++e.position);s.push(e.input.slice(t,e.position))}0!==r&&P(e),c.call(L,i)?L[i](e,i,s):N(e,'unknown document directive "'+i+'"')}G(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,G(e,!0,-1)):a&&F(e,"directives end mark is expected"),Y(e,e.lineIndent-1,u,!1,!0),G(e,!0,-1),e.checkLineBreaks&&f.test(e.input.slice(n,e.position))&&N(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&j(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,G(e,!0,-1)):e.position{"use strict";var s=i(90560);function r(e,t,i,s,r){this.name=e,this.buffer=t,this.position=i,this.line=s,this.column=r}r.prototype.getSnippet=function(e,t){var i,r,n,a,o;if(!this.buffer)return null;for(e=e||4,t=t||75,i="",r=this.position;r>0&&-1==="\0\r\n…\u2028\u2029".indexOf(this.buffer.charAt(r-1));)if(r-=1,this.position-r>t/2-1){i=" ... ",r+=5;break}for(n="",a=this.position;at/2-1){n=" ... ",a-=5;break}return o=this.buffer.slice(r,a),s.repeat(" ",e)+i+o+n+"\n"+s.repeat(" ",e+this.position-r+i.length)+"^"},r.prototype.toString=function(e){var t,i="";return this.name&&(i+='in "'+this.name+'" '),i+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet())&&(i+=":\n"+t),i},e.exports=r},38107:(e,t,i)=>{"use strict";var s=i(90560),r=i(86822),n=i(42257);function a(e,t,i){var s=[];return e.include.forEach((function(e){i=a(e,t,i)})),e[t].forEach((function(e){i.forEach((function(t,i){t.tag===e.tag&&t.kind===e.kind&&s.push(i)})),i.push(e)})),i.filter((function(e,t){return-1===s.indexOf(t)}))}function o(e){this.include=e.include||[],this.implicit=e.implicit||[],this.explicit=e.explicit||[],this.implicit.forEach((function(e){if(e.loadKind&&"scalar"!==e.loadKind)throw new r("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.")})),this.compiledImplicit=a(this,"implicit",[]),this.compiledExplicit=a(this,"explicit",[]),this.compiledTypeMap=function(){var e,t,i={scalar:{},sequence:{},mapping:{},fallback:{}};function s(e){i[e.kind][e.tag]=i.fallback[e.tag]=e}for(e=0,t=arguments.length;e{"use strict";var s=i(38107);e.exports=new s({include:[i(93886)]})},17386:(e,t,i)=>{"use strict";var s=i(38107);e.exports=s.DEFAULT=new s({include:[i(79251)],explicit:[i(7021),i(55844),i(1327)]})},79251:(e,t,i)=>{"use strict";var s=i(38107);e.exports=new s({include:[i(71514)],implicit:[i(1363),i(3174)],explicit:[i(81676),i(53900),i(21908),i(30768)]})},19777:(e,t,i)=>{"use strict";var s=i(38107);e.exports=new s({explicit:[i(99678),i(23371),i(17261)]})},93886:(e,t,i)=>{"use strict";var s=i(38107);e.exports=new s({include:[i(19777)],implicit:[i(40707),i(47847),i(84302),i(28249)]})},42257:(e,t,i)=>{"use strict";var s=i(86822),r=["kind","resolve","construct","instanceOf","predicate","represent","defaultStyle","styleAliases"],n=["scalar","sequence","mapping"];e.exports=function(e,t){var i,a;if(t=t||{},Object.keys(t).forEach((function(t){if(-1===r.indexOf(t))throw new s('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.defaultStyle=t.defaultStyle||null,this.styleAliases=(i=t.styleAliases||null,a={},null!==i&&Object.keys(i).forEach((function(e){i[e].forEach((function(t){a[String(t)]=e}))})),a),-1===n.indexOf(this.kind))throw new s('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')}},81676:(e,t,i)=>{"use strict";var s;try{s=i(14300).Buffer}catch(e){}var r=i(42257),n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";e.exports=new r("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,i,s=0,r=e.length,a=n;for(i=0;i64)){if(t<0)return!1;s+=6}return s%8==0},construct:function(e){var t,i,r=e.replace(/[\r\n=]/g,""),a=r.length,o=n,c=0,l=[];for(t=0;t>16&255),l.push(c>>8&255),l.push(255&c)),c=c<<6|o.indexOf(r.charAt(t));return 0==(i=a%4*6)?(l.push(c>>16&255),l.push(c>>8&255),l.push(255&c)):18===i?(l.push(c>>10&255),l.push(c>>2&255)):12===i&&l.push(c>>4&255),s?s.from?s.from(l):new s(l):l},predicate:function(e){return s&&s.isBuffer(e)},represent:function(e){var t,i,s="",r=0,a=e.length,o=n;for(t=0;t>18&63],s+=o[r>>12&63],s+=o[r>>6&63],s+=o[63&r]),r=(r<<8)+e[t];return 0==(i=a%3)?(s+=o[r>>18&63],s+=o[r>>12&63],s+=o[r>>6&63],s+=o[63&r]):2===i?(s+=o[r>>10&63],s+=o[r>>4&63],s+=o[r<<2&63],s+=o[64]):1===i&&(s+=o[r>>2&63],s+=o[r<<4&63],s+=o[64],s+=o[64]),s}})},47847:(e,t,i)=>{"use strict";var s=i(42257);e.exports=new s("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},28249:(e,t,i)=>{"use strict";var s=i(90560),r=i(42257),n=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$"),a=/^[-+]?[0-9]+e/;e.exports=new r("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!n.test(e)||"_"===e[e.length-1])},construct:function(e){var t,i,s,r;return i="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,r=[],"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===i?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:t.indexOf(":")>=0?(t.split(":").forEach((function(e){r.unshift(parseFloat(e,10))})),t=0,s=1,r.forEach((function(e){t+=e*s,s*=60})),i*t):i*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||s.isNegativeZero(e))},represent:function(e,t){var i;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(s.isNegativeZero(e))return"-0.0";return i=e.toString(10),a.test(i)?i.replace("e",".e"):i},defaultStyle:"lowercase"})},84302:(e,t,i)=>{"use strict";var s=i(90560),r=i(42257);function n(e){return 48<=e&&e<=55}function a(e){return 48<=e&&e<=57}e.exports=new r("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,i,s=e.length,r=0,o=!1;if(!s)return!1;if("-"!==(t=e[r])&&"+"!==t||(t=e[++r]),"0"===t){if(r+1===s)return!0;if("b"===(t=e[++r])){for(r++;r=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0"+e.toString(8):"-0"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})},1327:(e,t,i)=>{"use strict";var s;try{s=i(87491)}catch(e){"undefined"!=typeof window&&(s=window.esprima)}var r=i(42257);e.exports=new r("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:function(e){if(null===e)return!1;try{var t="("+e+")",i=s.parse(t,{range:!0});return"Program"===i.type&&1===i.body.length&&"ExpressionStatement"===i.body[0].type&&("ArrowFunctionExpression"===i.body[0].expression.type||"FunctionExpression"===i.body[0].expression.type)}catch(e){return!1}},construct:function(e){var t,i="("+e+")",r=s.parse(i,{range:!0}),n=[];if("Program"!==r.type||1!==r.body.length||"ExpressionStatement"!==r.body[0].type||"ArrowFunctionExpression"!==r.body[0].expression.type&&"FunctionExpression"!==r.body[0].expression.type)throw new Error("Failed to resolve function");return r.body[0].expression.params.forEach((function(e){n.push(e.name)})),t=r.body[0].expression.body.range,"BlockStatement"===r.body[0].expression.body.type?new Function(n,i.slice(t[0]+1,t[1]-1)):new Function(n,"return "+i.slice(t[0],t[1]))},predicate:function(e){return"[object Function]"===Object.prototype.toString.call(e)},represent:function(e){return e.toString()}})},55844:(e,t,i)=>{"use strict";var s=i(42257);e.exports=new s("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:function(e){if(null===e)return!1;if(0===e.length)return!1;var t=e,i=/\/([gim]*)$/.exec(e),s="";if("/"===t[0]){if(i&&(s=i[1]),s.length>3)return!1;if("/"!==t[t.length-s.length-1])return!1}return!0},construct:function(e){var t=e,i=/\/([gim]*)$/.exec(e),s="";return"/"===t[0]&&(i&&(s=i[1]),t=t.slice(1,t.length-s.length-1)),new RegExp(t,s)},predicate:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},represent:function(e){var t="/"+e.source+"/";return e.global&&(t+="g"),e.multiline&&(t+="m"),e.ignoreCase&&(t+="i"),t}})},7021:(e,t,i)=>{"use strict";var s=i(42257);e.exports=new s("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:function(){return!0},construct:function(){},predicate:function(e){return void 0===e},represent:function(){return""}})},17261:(e,t,i)=>{"use strict";var s=i(42257);e.exports=new s("tag:yaml.org,2002:map",{kind:"mapping",construct:function(e){return null!==e?e:{}}})},3174:(e,t,i)=>{"use strict";var s=i(42257);e.exports=new s("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}})},40707:(e,t,i)=>{"use strict";var s=i(42257);e.exports=new s("tag:yaml.org,2002:null",{kind:"scalar",resolve:function(e){if(null===e)return!0;var t=e.length;return 1===t&&"~"===e||4===t&&("null"===e||"Null"===e||"NULL"===e)},construct:function(){return null},predicate:function(e){return null===e},represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})},53900:(e,t,i)=>{"use strict";var s=i(42257),r=Object.prototype.hasOwnProperty,n=Object.prototype.toString;e.exports=new s("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,i,s,a,o,c=[],l=e;for(t=0,i=l.length;t{"use strict";var s=i(42257),r=Object.prototype.toString;e.exports=new s("tag:yaml.org,2002:pairs",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,i,s,n,a,o=e;for(a=new Array(o.length),t=0,i=o.length;t{"use strict";var s=i(42257);e.exports=new s("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(e){return null!==e?e:[]}})},30768:(e,t,i)=>{"use strict";var s=i(42257),r=Object.prototype.hasOwnProperty;e.exports=new s("tag:yaml.org,2002:set",{kind:"mapping",resolve:function(e){if(null===e)return!0;var t,i=e;for(t in i)if(r.call(i,t)&&null!==i[t])return!1;return!0},construct:function(e){return null!==e?e:{}}})},99678:(e,t,i)=>{"use strict";var s=i(42257);e.exports=new s("tag:yaml.org,2002:str",{kind:"scalar",construct:function(e){return null!==e?e:""}})},1363:(e,t,i)=>{"use strict";var s=i(42257),r=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),n=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");e.exports=new s("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function(e){return null!==e&&(null!==r.exec(e)||null!==n.exec(e))},construct:function(e){var t,i,s,a,o,c,l,p,A=0,u=null;if(null===(t=r.exec(e))&&(t=n.exec(e)),null===t)throw new Error("Date resolve error");if(i=+t[1],s=+t[2]-1,a=+t[3],!t[4])return new Date(Date.UTC(i,s,a));if(o=+t[4],c=+t[5],l=+t[6],t[7]){for(A=t[7].slice(0,3);A.length<3;)A+="0";A=+A}return t[9]&&(u=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(u=-u)),p=new Date(Date.UTC(i,s,a,o,c,l,A)),u&&p.setTime(p.getTime()-u),p},instanceOf:Date,represent:function(e){return e.toISOString()}})},20097:(e,t,i)=>{"use strict";var s=i(37980),r=i(99719);function n(e,t){return function(){throw new Error("Function yaml."+e+" is removed in js-yaml 4. Use yaml."+t+" instead, which is now safe by default.")}}e.exports.Type=i(95344),e.exports.Schema=i(42459),e.exports.FAILSAFE_SCHEMA=i(69366),e.exports.JSON_SCHEMA=i(74068),e.exports.CORE_SCHEMA=i(75243),e.exports.DEFAULT_SCHEMA=i(66392),e.exports.load=s.load,e.exports.loadAll=s.loadAll,e.exports.dump=r.dump,e.exports.YAMLException=i(34699),e.exports.types={binary:i(3389),float:i(25948),map:i(69274),null:i(91237),pairs:i(97988),set:i(576),timestamp:i(26405),bool:i(93691),int:i(30492),merge:i(30945),omap:i(4868),seq:i(56821),str:i(43432)},e.exports.safeLoad=n("safeLoad","load"),e.exports.safeLoadAll=n("safeLoadAll","loadAll"),e.exports.safeDump=n("safeDump","dump")},11392:e=>{"use strict";function t(e){return null==e}e.exports.isNothing=t,e.exports.isObject=function(e){return"object"==typeof e&&null!==e},e.exports.toArray=function(e){return Array.isArray(e)?e:t(e)?[]:[e]},e.exports.repeat=function(e,t){var i,s="";for(i=0;i{"use strict";var s=i(11392),r=i(34699),n=i(66392),a=Object.prototype.toString,o=Object.prototype.hasOwnProperty,c=65279,l=9,p=10,A=13,u=32,d=33,h=34,m=35,g=37,f=38,E=39,C=42,y=44,v=45,w=58,I=61,B=62,b=63,Q=64,x=91,k=93,D=96,S=123,_=124,R=125,T={0:"\\0",7:"\\a",8:"\\b",9:"\\t",10:"\\n",11:"\\v",12:"\\f",13:"\\r",27:"\\e",34:'\\"',92:"\\\\",133:"\\N",160:"\\_",8232:"\\L",8233:"\\P"},F=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"],N=/^[-+]?[0-9_]+(?::[0-9_]+)+(?:\.[0-9_]*)?$/;function L(e){var t,i,n;if(t=e.toString(16).toUpperCase(),e<=255)i="x",n=2;else if(e<=65535)i="u",n=4;else{if(!(e<=4294967295))throw new r("code point within a string may not be greater than 0xFFFFFFFF");i="U",n=8}return"\\"+i+s.repeat("0",n-t.length)+t}var O=2;function M(e){this.schema=e.schema||n,this.indent=Math.max(1,e.indent||2),this.noArrayIndent=e.noArrayIndent||!1,this.skipInvalid=e.skipInvalid||!1,this.flowLevel=s.isNothing(e.flowLevel)?-1:e.flowLevel,this.styleMap=function(e,t){var i,s,r,n,a,c,l;if(null===t)return{};for(i={},r=0,n=(s=Object.keys(t)).length;r=55296&&s<=56319&&t+1=56320&&i<=57343?1024*(s-55296)+i-56320+65536:s}function q(e){return/^\n* /.test(e)}var Y=1,W=2,z=3,$=4,X=5;function K(e,t,i,s,n){e.dump=function(){if(0===t.length)return e.quotingType===O?'""':"''";if(!e.noCompatMode&&(-1!==F.indexOf(t)||N.test(t)))return e.quotingType===O?'"'+t+'"':"'"+t+"'";var a=e.indent*Math.max(1,i),o=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-a),l=s||e.flowLevel>-1&&i>=e.flowLevel;switch(function(e,t,i,s,r,n,a,o){var l,A,u=0,T=null,F=!1,N=!1,L=-1!==s,M=-1,U=j(A=H(e,0))&&A!==c&&!G(A)&&A!==v&&A!==b&&A!==w&&A!==y&&A!==x&&A!==k&&A!==S&&A!==R&&A!==m&&A!==f&&A!==C&&A!==d&&A!==_&&A!==I&&A!==B&&A!==E&&A!==h&&A!==g&&A!==Q&&A!==D&&function(e){return!G(e)&&e!==w}(H(e,e.length-1));if(t||a)for(l=0;l=65536?l+=2:l++){if(!j(u=H(e,l)))return X;U=U&&J(u,T,o),T=u}else{for(l=0;l=65536?l+=2:l++){if((u=H(e,l))===p)F=!0,L&&(N=N||l-M-1>s&&" "!==e[M+1],M=l);else if(!j(u))return X;U=U&&J(u,T,o),T=u}N=N||L&&l-M-1>s&&" "!==e[M+1]}return F||N?i>9&&q(e)?X:a?n===O?X:W:N?$:z:!U||a||r(e)?n===O?X:W:Y}(t,l,e.indent,o,(function(t){return function(e,t){var i,s;for(i=0,s=e.implicitTypes.length;i"+Z(t,e.indent)+ee(U(function(e,t){for(var i,s,r,n=/(\n+)([^\n]*)/g,a=(r=-1!==(r=e.indexOf("\n"))?r:e.length,n.lastIndex=r,te(e.slice(0,r),t)),o="\n"===e[0]||" "===e[0];s=n.exec(e);){var c=s[1],l=s[2];i=" "===l[0],a+=c+(o||i||""===l?"":"\n")+te(l,t),o=i}return a}(t,o),a));case X:return'"'+function(e){for(var t,i="",s=0,r=0;r=65536?r+=2:r++)s=H(e,r),!(t=T[s])&&j(s)?(i+=e[r],s>=65536&&(i+=e[r+1])):i+=t||L(s);return i}(t)+'"';default:throw new r("impossible error: invalid scalar style")}}()}function Z(e,t){var i=q(e)?String(t):"",s="\n"===e[e.length-1];return i+(!s||"\n"!==e[e.length-2]&&"\n"!==e?s?"":"-":"+")+"\n"}function ee(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function te(e,t){if(""===e||" "===e[0])return e;for(var i,s,r=/ [^ ]/g,n=0,a=0,o=0,c="";i=r.exec(e);)(o=i.index)-n>t&&(s=a>n?a:o,c+="\n"+e.slice(n,s),n=s+1),a=o;return c+="\n",e.length-n>t&&a>n?c+=e.slice(n,a)+"\n"+e.slice(a+1):c+=e.slice(n),c.slice(1)}function ie(e,t,i,s){var r,n,a,o="",c=e.tag;for(r=0,n=i.length;r tag resolver accepts not "'+A+'" style');s=p.represent[A](t,A)}e.dump=s}return!0}return!1}function re(e,t,i,s,n,o,c){e.tag=null,e.dump=i,se(e,i,!1)||se(e,i,!0);var l,A=a.call(e.dump),u=s;s&&(s=e.flowLevel<0||e.flowLevel>t);var d,h,m="[object Object]"===A||"[object Array]"===A;if(m&&(h=-1!==(d=e.duplicates.indexOf(i))),(null!==e.tag&&"?"!==e.tag||h||2!==e.indent&&t>0)&&(n=!1),h&&e.usedDuplicates[d])e.dump="*ref_"+d;else{if(m&&h&&!e.usedDuplicates[d]&&(e.usedDuplicates[d]=!0),"[object Object]"===A)s&&0!==Object.keys(e.dump).length?(function(e,t,i,s){var n,a,o,c,l,A,u="",d=e.tag,h=Object.keys(i);if(!0===e.sortKeys)h.sort();else if("function"==typeof e.sortKeys)h.sort(e.sortKeys);else if(e.sortKeys)throw new r("sortKeys must be a boolean or a function");for(n=0,a=h.length;n1024)&&(e.dump&&p===e.dump.charCodeAt(0)?A+="?":A+="? "),A+=e.dump,l&&(A+=P(e,t)),re(e,t+1,c,!0,l)&&(e.dump&&p===e.dump.charCodeAt(0)?A+=":":A+=": ",u+=A+=e.dump));e.tag=d,e.dump=u||"{}"}(e,t,e.dump,n),h&&(e.dump="&ref_"+d+e.dump)):(function(e,t,i){var s,r,n,a,o,c="",l=e.tag,p=Object.keys(i);for(s=0,r=p.length;s1024&&(o+="? "),o+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),re(e,t,a,!1,!1)&&(c+=o+=e.dump));e.tag=l,e.dump="{"+c+"}"}(e,t,e.dump),h&&(e.dump="&ref_"+d+" "+e.dump));else if("[object Array]"===A)s&&0!==e.dump.length?(e.noArrayIndent&&!c&&t>0?ie(e,t-1,e.dump,n):ie(e,t,e.dump,n),h&&(e.dump="&ref_"+d+e.dump)):(function(e,t,i){var s,r,n,a="",o=e.tag;for(s=0,r=i.length;s",e.dump=l+" "+e.dump)}return!0}function ne(e,t){var i,s,r=[],n=[];for(ae(e,r,n),i=0,s=n.length;i{"use strict";function t(e,t){var i="",s=e.reason||"(unknown reason)";return e.mark?(e.mark.name&&(i+='in "'+e.mark.name+'" '),i+="("+(e.mark.line+1)+":"+(e.mark.column+1)+")",!t&&e.mark.snippet&&(i+="\n\n"+e.mark.snippet),s+" "+i):s}function i(e,i){Error.call(this),this.name="YAMLException",this.reason=e,this.mark=i,this.message=t(this,!1),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack||""}i.prototype=Object.create(Error.prototype),i.prototype.constructor=i,i.prototype.toString=function(e){return this.name+": "+t(this,e)},e.exports=i},37980:(e,t,i)=>{"use strict";var s=i(11392),r=i(34699),n=i(25638),a=i(66392),o=Object.prototype.hasOwnProperty,c=1,l=2,p=3,A=4,u=1,d=2,h=3,m=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,g=/[\x85\u2028\u2029]/,f=/[,\[\]\{\}]/,E=/^(?:!|!!|![a-z\-]+!)$/i,C=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function y(e){return Object.prototype.toString.call(e)}function v(e){return 10===e||13===e}function w(e){return 9===e||32===e}function I(e){return 9===e||32===e||10===e||13===e}function B(e){return 44===e||91===e||93===e||123===e||125===e}function b(e){var t;return 48<=e&&e<=57?e-48:97<=(t=32|e)&&t<=102?t-97+10:-1}function Q(e){return 48===e?"\0":97===e?"":98===e?"\b":116===e||9===e?"\t":110===e?"\n":118===e?"\v":102===e?"\f":114===e?"\r":101===e?"":32===e?" ":34===e?'"':47===e?"/":92===e?"\\":78===e?"…":95===e?" ":76===e?"\u2028":80===e?"\u2029":""}function x(e){return e<=65535?String.fromCharCode(e):String.fromCharCode(55296+(e-65536>>10),56320+(e-65536&1023))}for(var k=new Array(256),D=new Array(256),S=0;S<256;S++)k[S]=Q(S)?1:0,D[S]=Q(S);function _(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||a,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.firstTabInLine=-1,this.documents=[]}function R(e,t){var i={name:e.filename,buffer:e.input.slice(0,-1),position:e.position,line:e.line,column:e.position-e.lineStart};return i.snippet=n(i),new r(t,i)}function T(e,t){throw R(e,t)}function F(e,t){e.onWarning&&e.onWarning.call(null,R(e,t))}var N={YAML:function(e,t,i){var s,r,n;null!==e.version&&T(e,"duplication of %YAML directive"),1!==i.length&&T(e,"YAML directive accepts exactly one argument"),null===(s=/^([0-9]+)\.([0-9]+)$/.exec(i[0]))&&T(e,"ill-formed argument of the YAML directive"),r=parseInt(s[1],10),n=parseInt(s[2],10),1!==r&&T(e,"unacceptable YAML version of the document"),e.version=i[0],e.checkLineBreaks=n<2,1!==n&&2!==n&&F(e,"unsupported YAML version of the document")},TAG:function(e,t,i){var s,r;2!==i.length&&T(e,"TAG directive accepts exactly two arguments"),s=i[0],r=i[1],E.test(s)||T(e,"ill-formed tag handle (first argument) of the TAG directive"),o.call(e.tagMap,s)&&T(e,'there is a previously declared suffix for "'+s+'" tag handle'),C.test(r)||T(e,"ill-formed tag prefix (second argument) of the TAG directive");try{r=decodeURIComponent(r)}catch(t){T(e,"tag prefix is malformed: "+r)}e.tagMap[s]=r}};function L(e,t,i,s){var r,n,a,o;if(t1&&(e.result+=s.repeat("\n",t-1))}function V(e,t){var i,s,r=e.tag,n=e.anchor,a=[],o=!1;if(-1!==e.firstTabInLine)return!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=a),s=e.input.charCodeAt(e.position);0!==s&&(-1!==e.firstTabInLine&&(e.position=e.firstTabInLine,T(e,"tab characters must not be used in indentation")),45===s)&&I(e.input.charCodeAt(e.position+1));)if(o=!0,e.position++,P(e,!0,-1)&&e.lineIndent<=t)a.push(null),s=e.input.charCodeAt(e.position);else if(i=e.line,q(e,t,p,!1,!0),a.push(e.result),P(e,!0,-1),s=e.input.charCodeAt(e.position),(e.line===i||e.lineIndent>t)&&0!==s)T(e,"bad indentation of a sequence entry");else if(e.lineIndentt?_=1:e.lineIndent===t?_=0:e.lineIndentt?_=1:e.lineIndent===t?_=0:e.lineIndentt)&&(C&&(a=e.line,o=e.lineStart,c=e.position),q(e,t,A,!0,r)&&(C?f=e.result:E=e.result),C||(M(e,h,m,g,f,E,a,o,c),g=f=E=null),P(e,!0,-1),p=e.input.charCodeAt(e.position)),(e.line===n||e.lineIndent>t)&&0!==p)T(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===n?T(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):p?T(e,"repeat of an indentation width identifier"):(A=t+n-1,p=!0)}if(w(a)){do{a=e.input.charCodeAt(++e.position)}while(w(a));if(35===a)do{a=e.input.charCodeAt(++e.position)}while(!v(a)&&0!==a)}for(;0!==a;){for(U(e),e.lineIndent=0,a=e.input.charCodeAt(e.position);(!p||e.lineIndentA&&(A=e.lineIndent),v(a))m++;else{if(e.lineIndent0){for(r=a,n=0;r>0;r--)(a=b(o=e.input.charCodeAt(++e.position)))>=0?n=(n<<4)+a:T(e,"expected hexadecimal character");e.result+=x(n),e.position++}else T(e,"unknown escape sequence");i=s=e.position}else v(o)?(L(e,i,s,!0),j(e,P(e,!1,t)),i=s=e.position):e.position===e.lineStart&&G(e)?T(e,"unexpected end of the document within a double quoted scalar"):(e.position++,s=e.position)}T(e,"unexpected end of the stream within a double quoted scalar")}(e,Q)?F=!0:function(e){var t,i,s;if(42!==(s=e.input.charCodeAt(e.position)))return!1;for(s=e.input.charCodeAt(++e.position),t=e.position;0!==s&&!I(s)&&!B(s);)s=e.input.charCodeAt(++e.position);return e.position===t&&T(e,"name of an alias node must contain at least one character"),i=e.input.slice(t,e.position),o.call(e.anchorMap,i)||T(e,'unidentified alias "'+i+'"'),e.result=e.anchorMap[i],P(e,!0,-1),!0}(e)?(F=!0,null===e.tag&&null===e.anchor||T(e,"alias node should not have any properties")):function(e,t,i){var s,r,n,a,o,c,l,p,A=e.kind,u=e.result;if(I(p=e.input.charCodeAt(e.position))||B(p)||35===p||38===p||42===p||33===p||124===p||62===p||39===p||34===p||37===p||64===p||96===p)return!1;if((63===p||45===p)&&(I(s=e.input.charCodeAt(e.position+1))||i&&B(s)))return!1;for(e.kind="scalar",e.result="",r=n=e.position,a=!1;0!==p;){if(58===p){if(I(s=e.input.charCodeAt(e.position+1))||i&&B(s))break}else if(35===p){if(I(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&G(e)||i&&B(p))break;if(v(p)){if(o=e.line,c=e.lineStart,l=e.lineIndent,P(e,!1,-1),e.lineIndent>=t){a=!0,p=e.input.charCodeAt(e.position);continue}e.position=n,e.line=o,e.lineStart=c,e.lineIndent=l;break}}a&&(L(e,r,n,!1),j(e,e.line-o),r=n=e.position,a=!1),w(p)||(n=e.position+1),p=e.input.charCodeAt(++e.position)}return L(e,r,n,!1),!!e.result||(e.kind=A,e.result=u,!1)}(e,Q,c===i)&&(F=!0,null===e.tag&&(e.tag="?")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===_&&(F=g&&V(e,S))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&T(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),f=0,E=e.implicitTypes.length;f"),null!==e.result&&y.kind!==e.kind&&T(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+y.kind+'", not "'+e.kind+'"'),y.resolve(e.result,e.tag)?(e.result=y.construct(e.result,e.tag),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):T(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||F}function Y(e){var t,i,s,r,n=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap=Object.create(null),e.anchorMap=Object.create(null);0!==(r=e.input.charCodeAt(e.position))&&(P(e,!0,-1),r=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==r));){for(a=!0,r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!I(r);)r=e.input.charCodeAt(++e.position);for(s=[],(i=e.input.slice(t,e.position)).length<1&&T(e,"directive name must not be less than one character in length");0!==r;){for(;w(r);)r=e.input.charCodeAt(++e.position);if(35===r){do{r=e.input.charCodeAt(++e.position)}while(0!==r&&!v(r));break}if(v(r))break;for(t=e.position;0!==r&&!I(r);)r=e.input.charCodeAt(++e.position);s.push(e.input.slice(t,e.position))}0!==r&&U(e),o.call(N,i)?N[i](e,i,s):F(e,'unknown document directive "'+i+'"')}P(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,P(e,!0,-1)):a&&T(e,"directives end mark is expected"),q(e,e.lineIndent-1,A,!1,!0),P(e,!0,-1),e.checkLineBreaks&&g.test(e.input.slice(n,e.position))&&F(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&G(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,P(e,!0,-1)):e.position{"use strict";var s=i(34699),r=i(95344);function n(e,t){var i=[];return e[t].forEach((function(e){var t=i.length;i.forEach((function(i,s){i.tag===e.tag&&i.kind===e.kind&&i.multi===e.multi&&(t=s)})),i[t]=e})),i}function a(e){return this.extend(e)}a.prototype.extend=function(e){var t=[],i=[];if(e instanceof r)i.push(e);else if(Array.isArray(e))i=i.concat(e);else{if(!e||!Array.isArray(e.implicit)&&!Array.isArray(e.explicit))throw new s("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");e.implicit&&(t=t.concat(e.implicit)),e.explicit&&(i=i.concat(e.explicit))}t.forEach((function(e){if(!(e instanceof r))throw new s("Specified list of YAML types (or a single Type object) contains a non-Type object.");if(e.loadKind&&"scalar"!==e.loadKind)throw new s("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");if(e.multi)throw new s("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.")})),i.forEach((function(e){if(!(e instanceof r))throw new s("Specified list of YAML types (or a single Type object) contains a non-Type object.")}));var o=Object.create(a.prototype);return o.implicit=(this.implicit||[]).concat(t),o.explicit=(this.explicit||[]).concat(i),o.compiledImplicit=n(o,"implicit"),o.compiledExplicit=n(o,"explicit"),o.compiledTypeMap=function(){var e,t,i={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}};function s(e){e.multi?(i.multi[e.kind].push(e),i.multi.fallback.push(e)):i[e.kind][e.tag]=i.fallback[e.tag]=e}for(e=0,t=arguments.length;e{"use strict";e.exports=i(74068)},66392:(e,t,i)=>{"use strict";e.exports=i(75243).extend({implicit:[i(26405),i(30945)],explicit:[i(3389),i(4868),i(97988),i(576)]})},69366:(e,t,i)=>{"use strict";var s=i(42459);e.exports=new s({explicit:[i(43432),i(56821),i(69274)]})},74068:(e,t,i)=>{"use strict";e.exports=i(69366).extend({implicit:[i(91237),i(93691),i(30492),i(25948)]})},25638:(e,t,i)=>{"use strict";var s=i(11392);function r(e,t,i,s,r){var n="",a="",o=Math.floor(r/2)-1;return s-t>o&&(t=s-o+(n=" ... ").length),i-s>o&&(i=s+o-(a=" ...").length),{str:n+e.slice(t,i).replace(/\t/g,"→")+a,pos:s-t+n.length}}function n(e,t){return s.repeat(" ",t-e.length)+e}e.exports=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var i,a=/\r?\n|\r|\0/g,o=[0],c=[],l=-1;i=a.exec(e.buffer);)c.push(i.index),o.push(i.index+i[0].length),e.position<=i.index&&l<0&&(l=o.length-2);l<0&&(l=o.length-1);var p,A,u="",d=Math.min(e.line+t.linesAfter,c.length).toString().length,h=t.maxLength-(t.indent+d+3);for(p=1;p<=t.linesBefore&&!(l-p<0);p++)A=r(e.buffer,o[l-p],c[l-p],e.position-(o[l]-o[l-p]),h),u=s.repeat(" ",t.indent)+n((e.line-p+1).toString(),d)+" | "+A.str+"\n"+u;for(A=r(e.buffer,o[l],c[l],e.position,h),u+=s.repeat(" ",t.indent)+n((e.line+1).toString(),d)+" | "+A.str+"\n",u+=s.repeat("-",t.indent+d+3+A.pos)+"^\n",p=1;p<=t.linesAfter&&!(l+p>=c.length);p++)A=r(e.buffer,o[l+p],c[l+p],e.position-(o[l]-o[l+p]),h),u+=s.repeat(" ",t.indent)+n((e.line+p+1).toString(),d)+" | "+A.str+"\n";return u.replace(/\n$/,"")}},95344:(e,t,i)=>{"use strict";var s=i(34699),r=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],n=["scalar","sequence","mapping"];e.exports=function(e,t){var i,a;if(t=t||{},Object.keys(t).forEach((function(t){if(-1===r.indexOf(t))throw new s('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=(i=t.styleAliases||null,a={},null!==i&&Object.keys(i).forEach((function(e){i[e].forEach((function(t){a[String(t)]=e}))})),a),-1===n.indexOf(this.kind))throw new s('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')}},3389:(e,t,i)=>{"use strict";var s=i(95344),r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";e.exports=new s("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,i,s=0,n=e.length,a=r;for(i=0;i64)){if(t<0)return!1;s+=6}return s%8==0},construct:function(e){var t,i,s=e.replace(/[\r\n=]/g,""),n=s.length,a=r,o=0,c=[];for(t=0;t>16&255),c.push(o>>8&255),c.push(255&o)),o=o<<6|a.indexOf(s.charAt(t));return 0==(i=n%4*6)?(c.push(o>>16&255),c.push(o>>8&255),c.push(255&o)):18===i?(c.push(o>>10&255),c.push(o>>2&255)):12===i&&c.push(o>>4&255),new Uint8Array(c)},predicate:function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)},represent:function(e){var t,i,s="",n=0,a=e.length,o=r;for(t=0;t>18&63],s+=o[n>>12&63],s+=o[n>>6&63],s+=o[63&n]),n=(n<<8)+e[t];return 0==(i=a%3)?(s+=o[n>>18&63],s+=o[n>>12&63],s+=o[n>>6&63],s+=o[63&n]):2===i?(s+=o[n>>10&63],s+=o[n>>4&63],s+=o[n<<2&63],s+=o[64]):1===i&&(s+=o[n>>2&63],s+=o[n<<4&63],s+=o[64],s+=o[64]),s}})},93691:(e,t,i)=>{"use strict";var s=i(95344);e.exports=new s("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},25948:(e,t,i)=>{"use strict";var s=i(11392),r=i(95344),n=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$"),a=/^[-+]?[0-9]+e/;e.exports=new r("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!n.test(e)||"_"===e[e.length-1])},construct:function(e){var t,i;return i="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===i?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:i*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||s.isNegativeZero(e))},represent:function(e,t){var i;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(s.isNegativeZero(e))return"-0.0";return i=e.toString(10),a.test(i)?i.replace("e",".e"):i},defaultStyle:"lowercase"})},30492:(e,t,i)=>{"use strict";var s=i(11392),r=i(95344);function n(e){return 48<=e&&e<=55}function a(e){return 48<=e&&e<=57}e.exports=new r("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,i,s=e.length,r=0,o=!1;if(!s)return!1;if("-"!==(t=e[r])&&"+"!==t||(t=e[++r]),"0"===t){if(r+1===s)return!0;if("b"===(t=e[++r])){for(r++;r=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0o"+e.toString(8):"-0o"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})},69274:(e,t,i)=>{"use strict";var s=i(95344);e.exports=new s("tag:yaml.org,2002:map",{kind:"mapping",construct:function(e){return null!==e?e:{}}})},30945:(e,t,i)=>{"use strict";var s=i(95344);e.exports=new s("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}})},91237:(e,t,i)=>{"use strict";var s=i(95344);e.exports=new s("tag:yaml.org,2002:null",{kind:"scalar",resolve:function(e){if(null===e)return!0;var t=e.length;return 1===t&&"~"===e||4===t&&("null"===e||"Null"===e||"NULL"===e)},construct:function(){return null},predicate:function(e){return null===e},represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"},empty:function(){return""}},defaultStyle:"lowercase"})},4868:(e,t,i)=>{"use strict";var s=i(95344),r=Object.prototype.hasOwnProperty,n=Object.prototype.toString;e.exports=new s("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,i,s,a,o,c=[],l=e;for(t=0,i=l.length;t{"use strict";var s=i(95344),r=Object.prototype.toString;e.exports=new s("tag:yaml.org,2002:pairs",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,i,s,n,a,o=e;for(a=new Array(o.length),t=0,i=o.length;t{"use strict";var s=i(95344);e.exports=new s("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(e){return null!==e?e:[]}})},576:(e,t,i)=>{"use strict";var s=i(95344),r=Object.prototype.hasOwnProperty;e.exports=new s("tag:yaml.org,2002:set",{kind:"mapping",resolve:function(e){if(null===e)return!0;var t,i=e;for(t in i)if(r.call(i,t)&&null!==i[t])return!1;return!0},construct:function(e){return null!==e?e:{}}})},43432:(e,t,i)=>{"use strict";var s=i(95344);e.exports=new s("tag:yaml.org,2002:str",{kind:"scalar",construct:function(e){return null!==e?e:""}})},26405:(e,t,i)=>{"use strict";var s=i(95344),r=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),n=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");e.exports=new s("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function(e){return null!==e&&(null!==r.exec(e)||null!==n.exec(e))},construct:function(e){var t,i,s,a,o,c,l,p,A=0,u=null;if(null===(t=r.exec(e))&&(t=n.exec(e)),null===t)throw new Error("Date resolve error");if(i=+t[1],s=+t[2]-1,a=+t[3],!t[4])return new Date(Date.UTC(i,s,a));if(o=+t[4],c=+t[5],l=+t[6],t[7]){for(A=t[7].slice(0,3);A.length<3;)A+="0";A=+A}return t[9]&&(u=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(u=-u)),p=new Date(Date.UTC(i,s,a,o,c,l,A)),u&&p.setTime(p.getTime()-u),p},instanceOf:Date,represent:function(e){return e.toISOString()}})},79049:e=>{var t=Object.prototype.toString;function i(e){return"function"==typeof e.constructor?e.constructor.name:null}e.exports=function(e){if(void 0===e)return"undefined";if(null===e)return"null";var s=typeof e;if("boolean"===s)return"boolean";if("string"===s)return"string";if("number"===s)return"number";if("symbol"===s)return"symbol";if("function"===s)return"GeneratorFunction"===i(e)?"generatorfunction":"function";if(function(e){return Array.isArray?Array.isArray(e):e instanceof Array}(e))return"array";if(function(e){return!(!e.constructor||"function"!=typeof e.constructor.isBuffer)&&e.constructor.isBuffer(e)}(e))return"buffer";if(function(e){try{if("number"==typeof e.length&&"function"==typeof e.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(e))return"arguments";if(function(e){return e instanceof Date||"function"==typeof e.toDateString&&"function"==typeof e.getDate&&"function"==typeof e.setDate}(e))return"date";if(function(e){return e instanceof Error||"string"==typeof e.message&&e.constructor&&"number"==typeof e.constructor.stackTraceLimit}(e))return"error";if(function(e){return e instanceof RegExp||"string"==typeof e.flags&&"boolean"==typeof e.ignoreCase&&"boolean"==typeof e.multiline&&"boolean"==typeof e.global}(e))return"regexp";switch(i(e)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(e){return"function"==typeof e.throw&&"function"==typeof e.return&&"function"==typeof e.next}(e))return"generator";switch(s=t.call(e)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return s.slice(8,-1).toLowerCase().replace(/\s/g,"")}},6853:(e,t,i)=>{"use strict";const s=i(47309),r=i(5881),n={info:s.blue("ℹ"),success:s.green("✔"),warning:s.yellow("⚠"),error:s.red("✖")},a={info:s.blue("i"),success:s.green("√"),warning:s.yellow("‼"),error:s.red("×")};e.exports=r()?n:a},29416:(e,t,i)=>{"use strict";const s=i(87406),r=Symbol("max"),n=Symbol("length"),a=Symbol("lengthCalculator"),o=Symbol("allowStale"),c=Symbol("maxAge"),l=Symbol("dispose"),p=Symbol("noDisposeOnSet"),A=Symbol("lruList"),u=Symbol("cache"),d=Symbol("updateAgeOnGet"),h=()=>1,m=(e,t,i)=>{const s=e[u].get(t);if(s){const t=s.value;if(g(e,t)){if(E(e,s),!e[o])return}else i&&(e[d]&&(s.value.now=Date.now()),e[A].unshiftNode(s));return t.value}},g=(e,t)=>{if(!t||!t.maxAge&&!e[c])return!1;const i=Date.now()-t.now;return t.maxAge?i>t.maxAge:e[c]&&i>e[c]},f=e=>{if(e[n]>e[r])for(let t=e[A].tail;e[n]>e[r]&&null!==t;){const i=t.prev;E(e,t),t=i}},E=(e,t)=>{if(t){const i=t.value;e[l]&&e[l](i.key,i.value),e[n]-=i.length,e[u].delete(i.key),e[A].removeNode(t)}};class C{constructor(e,t,i,s,r){this.key=e,this.value=t,this.length=i,this.now=s,this.maxAge=r||0}}const y=(e,t,i,s)=>{let r=i.value;g(e,r)&&(E(e,i),e[o]||(r=void 0)),r&&t.call(s,r.value,r.key,e)};e.exports=class{constructor(e){if("number"==typeof e&&(e={max:e}),e||(e={}),e.max&&("number"!=typeof e.max||e.max<0))throw new TypeError("max must be a non-negative number");this[r]=e.max||1/0;const t=e.length||h;if(this[a]="function"!=typeof t?h:t,this[o]=e.stale||!1,e.maxAge&&"number"!=typeof e.maxAge)throw new TypeError("maxAge must be a number");this[c]=e.maxAge||0,this[l]=e.dispose,this[p]=e.noDisposeOnSet||!1,this[d]=e.updateAgeOnGet||!1,this.reset()}set max(e){if("number"!=typeof e||e<0)throw new TypeError("max must be a non-negative number");this[r]=e||1/0,f(this)}get max(){return this[r]}set allowStale(e){this[o]=!!e}get allowStale(){return this[o]}set maxAge(e){if("number"!=typeof e)throw new TypeError("maxAge must be a non-negative number");this[c]=e,f(this)}get maxAge(){return this[c]}set lengthCalculator(e){"function"!=typeof e&&(e=h),e!==this[a]&&(this[a]=e,this[n]=0,this[A].forEach((e=>{e.length=this[a](e.value,e.key),this[n]+=e.length}))),f(this)}get lengthCalculator(){return this[a]}get length(){return this[n]}get itemCount(){return this[A].length}rforEach(e,t){t=t||this;for(let i=this[A].tail;null!==i;){const s=i.prev;y(this,e,i,t),i=s}}forEach(e,t){t=t||this;for(let i=this[A].head;null!==i;){const s=i.next;y(this,e,i,t),i=s}}keys(){return this[A].toArray().map((e=>e.key))}values(){return this[A].toArray().map((e=>e.value))}reset(){this[l]&&this[A]&&this[A].length&&this[A].forEach((e=>this[l](e.key,e.value))),this[u]=new Map,this[A]=new s,this[n]=0}dump(){return this[A].map((e=>!g(this,e)&&{k:e.key,v:e.value,e:e.now+(e.maxAge||0)})).toArray().filter((e=>e))}dumpLru(){return this[A]}set(e,t,i){if((i=i||this[c])&&"number"!=typeof i)throw new TypeError("maxAge must be a number");const s=i?Date.now():0,o=this[a](t,e);if(this[u].has(e)){if(o>this[r])return E(this,this[u].get(e)),!1;const a=this[u].get(e).value;return this[l]&&(this[p]||this[l](e,a.value)),a.now=s,a.maxAge=i,a.value=t,this[n]+=o-a.length,a.length=o,this.get(e),f(this),!0}const d=new C(e,t,o,s,i);return d.length>this[r]?(this[l]&&this[l](e,t),!1):(this[n]+=d.length,this[A].unshift(d),this[u].set(e,this[A].head),f(this),!0)}has(e){if(!this[u].has(e))return!1;const t=this[u].get(e).value;return!g(this,t)}get(e){return m(this,e,!0)}peek(e){return m(this,e,!1)}pop(){const e=this[A].tail;return e?(E(this,e),e.value):null}del(e){E(this,this[u].get(e))}load(e){this.reset();const t=Date.now();for(let i=e.length-1;i>=0;i--){const s=e[i],r=s.e||0;if(0===r)this.set(s.k,s.v);else{const e=r-t;e>0&&this.set(s.k,s.v,e)}}}prune(){this[u].forEach(((e,t)=>m(this,t,!1)))}}},69759:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});class i extends Error{}class s extends i{constructor(e){super(`Invalid DateTime: ${e.toMessage()}`)}}class r extends i{constructor(e){super(`Invalid Interval: ${e.toMessage()}`)}}class n extends i{constructor(e){super(`Invalid Duration: ${e.toMessage()}`)}}class a extends i{}class o extends i{constructor(e){super(`Invalid unit ${e}`)}}class c extends i{}class l extends i{constructor(){super("Zone is an abstract class")}}const p="numeric",A="short",u="long",d={year:p,month:p,day:p},h={year:p,month:A,day:p},m={year:p,month:A,day:p,weekday:A},g={year:p,month:u,day:p},f={year:p,month:u,day:p,weekday:u},E={hour:p,minute:p},C={hour:p,minute:p,second:p},y={hour:p,minute:p,second:p,timeZoneName:A},v={hour:p,minute:p,second:p,timeZoneName:u},w={hour:p,minute:p,hourCycle:"h23"},I={hour:p,minute:p,second:p,hourCycle:"h23"},B={hour:p,minute:p,second:p,hourCycle:"h23",timeZoneName:A},b={hour:p,minute:p,second:p,hourCycle:"h23",timeZoneName:u},Q={year:p,month:p,day:p,hour:p,minute:p},x={year:p,month:p,day:p,hour:p,minute:p,second:p},k={year:p,month:A,day:p,hour:p,minute:p},D={year:p,month:A,day:p,hour:p,minute:p,second:p},S={year:p,month:A,day:p,weekday:A,hour:p,minute:p},_={year:p,month:u,day:p,hour:p,minute:p,timeZoneName:A},R={year:p,month:u,day:p,hour:p,minute:p,second:p,timeZoneName:A},T={year:p,month:u,day:p,weekday:u,hour:p,minute:p,timeZoneName:u},F={year:p,month:u,day:p,weekday:u,hour:p,minute:p,second:p,timeZoneName:u};class N{get type(){throw new l}get name(){throw new l}get ianaName(){return this.name}get isUniversal(){throw new l}offsetName(e,t){throw new l}formatOffset(e,t){throw new l}offset(e){throw new l}equals(e){throw new l}get isValid(){throw new l}}let L=null;class O extends N{static get instance(){return null===L&&(L=new O),L}get type(){return"system"}get name(){return(new Intl.DateTimeFormat).resolvedOptions().timeZone}get isUniversal(){return!1}offsetName(e,{format:t,locale:i}){return $e(e,t,i)}formatOffset(e,t){return et(this.offset(e),t)}offset(e){return-new Date(e).getTimezoneOffset()}equals(e){return"system"===e.type}get isValid(){return!0}}let M={};const U={year:0,month:1,day:2,era:3,hour:4,minute:5,second:6};let P={};class G extends N{static create(e){return P[e]||(P[e]=new G(e)),P[e]}static resetCache(){P={},M={}}static isValidSpecifier(e){return this.isValidZone(e)}static isValidZone(e){if(!e)return!1;try{return new Intl.DateTimeFormat("en-US",{timeZone:e}).format(),!0}catch(e){return!1}}constructor(e){super(),this.zoneName=e,this.valid=G.isValidZone(e)}get type(){return"iana"}get name(){return this.zoneName}get isUniversal(){return!1}offsetName(e,{format:t,locale:i}){return $e(e,t,i,this.name)}formatOffset(e,t){return et(this.offset(e),t)}offset(e){const t=new Date(e);if(isNaN(t))return NaN;const i=(s=this.name,M[s]||(M[s]=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:s,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",era:"short"})),M[s]);var s;let[r,n,a,o,c,l,p]=i.formatToParts?function(e,t){const i=e.formatToParts(t),s=[];for(let e=0;e=0?u:1e3+u,(qe({year:r,month:n,day:a,hour:24===c?0:c,minute:l,second:p,millisecond:0})-A)/6e4}equals(e){return"iana"===e.type&&e.name===this.name}get isValid(){return this.valid}}let j={},V={};function J(e,t={}){const i=JSON.stringify([e,t]);let s=V[i];return s||(s=new Intl.DateTimeFormat(e,t),V[i]=s),s}let H={},q={},Y=null,W={};function z(e,t,i,s){const r=e.listingMode();return"error"===r?null:"en"===r?i(t):s(t)}class ${constructor(e,t,i){this.padTo=i.padTo||0,this.floor=i.floor||!1;const{padTo:s,floor:r,...n}=i;if(!t||Object.keys(n).length>0){const t={useGrouping:!1,...i};i.padTo>0&&(t.minimumIntegerDigits=i.padTo),this.inf=function(e,t={}){const i=JSON.stringify([e,t]);let s=H[i];return s||(s=new Intl.NumberFormat(e,t),H[i]=s),s}(e,t)}}format(e){if(this.inf){const t=this.floor?Math.floor(e):e;return this.inf.format(t)}return Me(this.floor?Math.floor(e):je(e,3),this.padTo)}}class X{constructor(e,t,i){let s;if(this.opts=i,this.originalZone=void 0,this.opts.timeZone)this.dt=e;else if("fixed"===e.zone.type){const t=e.offset/60*-1,i=t>=0?`Etc/GMT+${t}`:`Etc/GMT${t}`;0!==e.offset&&G.create(i).valid?(s=i,this.dt=e):(s="UTC",this.dt=0===e.offset?e:e.setZone("UTC").plus({minutes:e.offset}),this.originalZone=e.zone)}else"system"===e.zone.type?this.dt=e:"iana"===e.zone.type?(this.dt=e,s=e.zone.name):(s="UTC",this.dt=e.setZone("UTC").plus({minutes:e.offset}),this.originalZone=e.zone);const r={...this.opts};r.timeZone=r.timeZone||s,this.dtf=J(t,r)}format(){return this.originalZone?this.formatToParts().map((({value:e})=>e)).join(""):this.dtf.format(this.dt.toJSDate())}formatToParts(){const e=this.dtf.formatToParts(this.dt.toJSDate());return this.originalZone?e.map((e=>{if("timeZoneName"===e.type){const t=this.originalZone.offsetName(this.dt.ts,{locale:this.dt.locale,format:this.opts.timeZoneName});return{...e,value:t}}return e})):e}resolvedOptions(){return this.dtf.resolvedOptions()}}class K{constructor(e,t,i){this.opts={style:"long",...i},!t&&Re()&&(this.rtf=function(e,t={}){const{base:i,...s}=t,r=JSON.stringify([e,s]);let n=q[r];return n||(n=new Intl.RelativeTimeFormat(e,t),q[r]=n),n}(e,i))}format(e,t){return this.rtf?this.rtf.format(e,t):function(e,t,i="always",s=!1){const r={years:["year","yr."],quarters:["quarter","qtr."],months:["month","mo."],weeks:["week","wk."],days:["day","day","days"],hours:["hour","hr."],minutes:["minute","min."],seconds:["second","sec."]},n=-1===["hours","minutes","seconds"].indexOf(e);if("auto"===i&&n){const i="days"===e;switch(t){case 1:return i?"tomorrow":`next ${r[e][0]}`;case-1:return i?"yesterday":`last ${r[e][0]}`;case 0:return i?"today":`this ${r[e][0]}`}}const a=Object.is(t,-0)||t<0,o=Math.abs(t),c=1===o,l=r[e],p=s?c?l[1]:l[2]||l[1]:c?r[e][0]:e;return a?`${o} ${p} ago`:`in ${o} ${p}`}(t,e,this.opts.numeric,"long"!==this.opts.style)}formatToParts(e,t){return this.rtf?this.rtf.formatToParts(e,t):[]}}const Z={firstDay:1,minimalDays:4,weekend:[6,7]};class ee{static fromOpts(e){return ee.create(e.locale,e.numberingSystem,e.outputCalendar,e.weekSettings,e.defaultToEN)}static create(e,t,i,s,r=!1){const n=e||de.defaultLocale,a=n||(r?"en-US":Y||(Y=(new Intl.DateTimeFormat).resolvedOptions().locale,Y)),o=t||de.defaultNumberingSystem,c=i||de.defaultOutputCalendar,l=Le(s)||de.defaultWeekSettings;return new ee(a,o,c,l,n)}static resetCache(){Y=null,V={},H={},q={}}static fromObject({locale:e,numberingSystem:t,outputCalendar:i,weekSettings:s}={}){return ee.create(e,t,i,s)}constructor(e,t,i,s,r){const[n,a,o]=function(e){const t=e.indexOf("-x-");-1!==t&&(e=e.substring(0,t));const i=e.indexOf("-u-");if(-1===i)return[e];{let t,s;try{t=J(e).resolvedOptions(),s=e}catch(r){const n=e.substring(0,i);t=J(n).resolvedOptions(),s=n}const{numberingSystem:r,calendar:n}=t;return[s,r,n]}}(e);this.locale=n,this.numberingSystem=t||a||null,this.outputCalendar=i||o||null,this.weekSettings=s,this.intl=function(e,t,i){return i||t?(e.includes("-u-")||(e+="-u"),i&&(e+=`-ca-${i}`),t&&(e+=`-nu-${t}`),e):e}(this.locale,this.numberingSystem,this.outputCalendar),this.weekdaysCache={format:{},standalone:{}},this.monthsCache={format:{},standalone:{}},this.meridiemCache=null,this.eraCache={},this.specifiedLocale=r,this.fastNumbersCached=null}get fastNumbers(){var e;return null==this.fastNumbersCached&&(this.fastNumbersCached=(!(e=this).numberingSystem||"latn"===e.numberingSystem)&&("latn"===e.numberingSystem||!e.locale||e.locale.startsWith("en")||"latn"===new Intl.DateTimeFormat(e.intl).resolvedOptions().numberingSystem)),this.fastNumbersCached}listingMode(){const e=this.isEnglish(),t=!(null!==this.numberingSystem&&"latn"!==this.numberingSystem||null!==this.outputCalendar&&"gregory"!==this.outputCalendar);return e&&t?"en":"intl"}clone(e){return e&&0!==Object.getOwnPropertyNames(e).length?ee.create(e.locale||this.specifiedLocale,e.numberingSystem||this.numberingSystem,e.outputCalendar||this.outputCalendar,Le(e.weekSettings)||this.weekSettings,e.defaultToEN||!1):this}redefaultToEN(e={}){return this.clone({...e,defaultToEN:!0})}redefaultToSystem(e={}){return this.clone({...e,defaultToEN:!1})}months(e,t=!1){return z(this,e,nt,(()=>{const i=t?{month:e,day:"numeric"}:{month:e},s=t?"format":"standalone";return this.monthsCache[s][e]||(this.monthsCache[s][e]=function(e){const t=[];for(let i=1;i<=12;i++){const s=as.utc(2009,i,1);t.push(e(s))}return t}((e=>this.extract(e,i,"month")))),this.monthsCache[s][e]}))}weekdays(e,t=!1){return z(this,e,lt,(()=>{const i=t?{weekday:e,year:"numeric",month:"long",day:"numeric"}:{weekday:e},s=t?"format":"standalone";return this.weekdaysCache[s][e]||(this.weekdaysCache[s][e]=function(e){const t=[];for(let i=1;i<=7;i++){const s=as.utc(2016,11,13+i);t.push(e(s))}return t}((e=>this.extract(e,i,"weekday")))),this.weekdaysCache[s][e]}))}meridiems(){return z(this,void 0,(()=>pt),(()=>{if(!this.meridiemCache){const e={hour:"numeric",hourCycle:"h12"};this.meridiemCache=[as.utc(2016,11,13,9),as.utc(2016,11,13,19)].map((t=>this.extract(t,e,"dayperiod")))}return this.meridiemCache}))}eras(e){return z(this,e,ht,(()=>{const t={era:e};return this.eraCache[e]||(this.eraCache[e]=[as.utc(-40,1,1),as.utc(2017,1,1)].map((e=>this.extract(e,t,"era")))),this.eraCache[e]}))}extract(e,t,i){const s=this.dtFormatter(e,t).formatToParts().find((e=>e.type.toLowerCase()===i));return s?s.value:null}numberFormatter(e={}){return new $(this.intl,e.forceSimple||this.fastNumbers,e)}dtFormatter(e,t={}){return new X(e,this.intl,t)}relFormatter(e={}){return new K(this.intl,this.isEnglish(),e)}listFormatter(e={}){return function(e,t={}){const i=JSON.stringify([e,t]);let s=j[i];return s||(s=new Intl.ListFormat(e,t),j[i]=s),s}(this.intl,e)}isEnglish(){return"en"===this.locale||"en-us"===this.locale.toLowerCase()||new Intl.DateTimeFormat(this.intl).resolvedOptions().locale.startsWith("en-us")}getWeekSettings(){return this.weekSettings?this.weekSettings:Te()?function(e){let t=W[e];if(!t){const i=new Intl.Locale(e);t="getWeekInfo"in i?i.getWeekInfo():i.weekInfo,W[e]=t}return t}(this.locale):Z}getStartOfWeek(){return this.getWeekSettings().firstDay}getMinDaysInFirstWeek(){return this.getWeekSettings().minimalDays}getWeekendDays(){return this.getWeekSettings().weekend}equals(e){return this.locale===e.locale&&this.numberingSystem===e.numberingSystem&&this.outputCalendar===e.outputCalendar}}let te=null;class ie extends N{static get utcInstance(){return null===te&&(te=new ie(0)),te}static instance(e){return 0===e?ie.utcInstance:new ie(e)}static parseSpecifier(e){if(e){const t=e.match(/^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$/i);if(t)return new ie(Xe(t[1],t[2]))}return null}constructor(e){super(),this.fixed=e}get type(){return"fixed"}get name(){return 0===this.fixed?"UTC":`UTC${et(this.fixed,"narrow")}`}get ianaName(){return 0===this.fixed?"Etc/UTC":`Etc/GMT${et(-this.fixed,"narrow")}`}offsetName(){return this.name}formatOffset(e,t){return et(this.fixed,t)}get isUniversal(){return!0}offset(){return this.fixed}equals(e){return"fixed"===e.type&&e.fixed===this.fixed}get isValid(){return!0}}class se extends N{constructor(e){super(),this.zoneName=e}get type(){return"invalid"}get name(){return this.zoneName}get isUniversal(){return!1}offsetName(){return null}formatOffset(){return""}offset(){return NaN}equals(){return!1}get isValid(){return!1}}function re(e,t){if(De(e)||null===e)return t;if(e instanceof N)return e;if("string"==typeof e){const i=e.toLowerCase();return"default"===i?t:"local"===i||"system"===i?O.instance:"utc"===i||"gmt"===i?ie.utcInstance:ie.parseSpecifier(i)||G.create(e)}return Se(e)?ie.instance(e):"object"==typeof e&&"offset"in e&&"function"==typeof e.offset?e:new se(e)}let ne,ae=()=>Date.now(),oe="system",ce=null,le=null,pe=null,Ae=60,ue=null;class de{static get now(){return ae}static set now(e){ae=e}static set defaultZone(e){oe=e}static get defaultZone(){return re(oe,O.instance)}static get defaultLocale(){return ce}static set defaultLocale(e){ce=e}static get defaultNumberingSystem(){return le}static set defaultNumberingSystem(e){le=e}static get defaultOutputCalendar(){return pe}static set defaultOutputCalendar(e){pe=e}static get defaultWeekSettings(){return ue}static set defaultWeekSettings(e){ue=Le(e)}static get twoDigitCutoffYear(){return Ae}static set twoDigitCutoffYear(e){Ae=e%100}static get throwOnInvalid(){return ne}static set throwOnInvalid(e){ne=e}static resetCaches(){ee.resetCache(),G.resetCache()}}class he{constructor(e,t){this.reason=e,this.explanation=t}toMessage(){return this.explanation?`${this.reason}: ${this.explanation}`:this.reason}}const me=[0,31,59,90,120,151,181,212,243,273,304,334],ge=[0,31,60,91,121,152,182,213,244,274,305,335];function fe(e,t){return new he("unit out of range",`you specified ${t} (of type ${typeof t}) as a ${e}, which is invalid`)}function Ee(e,t,i){const s=new Date(Date.UTC(e,t-1,i));e<100&&e>=0&&s.setUTCFullYear(s.getUTCFullYear()-1900);const r=s.getUTCDay();return 0===r?7:r}function Ce(e,t,i){return i+(Ve(e)?ge:me)[t-1]}function ye(e,t){const i=Ve(e)?ge:me,s=i.findIndex((e=>eWe(s,t,i)?(c=s+1,l=1):c=s,{weekYear:c,weekNumber:l,weekday:o,...tt(e)}}function Ie(e,t=4,i=1){const{weekYear:s,weekNumber:r,weekday:n}=e,a=ve(Ee(s,1,t),i),o=Je(s);let c,l=7*r+n-a-7+t;l<1?(c=s-1,l+=Je(c)):l>o?(c=s+1,l-=Je(s)):c=s;const{month:p,day:A}=ye(c,l);return{year:c,month:p,day:A,...tt(e)}}function Be(e){const{year:t,month:i,day:s}=e;return{year:t,ordinal:Ce(t,i,s),...tt(e)}}function be(e){const{year:t,ordinal:i}=e,{month:s,day:r}=ye(t,i);return{year:t,month:s,day:r,...tt(e)}}function Qe(e,t){if(!De(e.localWeekday)||!De(e.localWeekNumber)||!De(e.localWeekYear)){if(!De(e.weekday)||!De(e.weekNumber)||!De(e.weekYear))throw new a("Cannot mix locale-based week fields with ISO-based week fields");return De(e.localWeekday)||(e.weekday=e.localWeekday),De(e.localWeekNumber)||(e.weekNumber=e.localWeekNumber),De(e.localWeekYear)||(e.weekYear=e.localWeekYear),delete e.localWeekday,delete e.localWeekNumber,delete e.localWeekYear,{minDaysInFirstWeek:t.getMinDaysInFirstWeek(),startOfWeek:t.getStartOfWeek()}}return{minDaysInFirstWeek:4,startOfWeek:1}}function xe(e){const t=_e(e.year),i=Oe(e.month,1,12),s=Oe(e.day,1,He(e.year,e.month));return t?i?!s&&fe("day",e.day):fe("month",e.month):fe("year",e.year)}function ke(e){const{hour:t,minute:i,second:s,millisecond:r}=e,n=Oe(t,0,23)||24===t&&0===i&&0===s&&0===r,a=Oe(i,0,59),o=Oe(s,0,59),c=Oe(r,0,999);return n?a?o?!c&&fe("millisecond",r):fe("second",s):fe("minute",i):fe("hour",t)}function De(e){return void 0===e}function Se(e){return"number"==typeof e}function _e(e){return"number"==typeof e&&e%1==0}function Re(){try{return"undefined"!=typeof Intl&&!!Intl.RelativeTimeFormat}catch(e){return!1}}function Te(){try{return"undefined"!=typeof Intl&&!!Intl.Locale&&("weekInfo"in Intl.Locale.prototype||"getWeekInfo"in Intl.Locale.prototype)}catch(e){return!1}}function Fe(e,t,i){if(0!==e.length)return e.reduce(((e,s)=>{const r=[t(s),s];return e&&i(e[0],r[0])===e[0]?e:r}),null)[1]}function Ne(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function Le(e){if(null==e)return null;if("object"!=typeof e)throw new c("Week settings must be an object");if(!Oe(e.firstDay,1,7)||!Oe(e.minimalDays,1,7)||!Array.isArray(e.weekend)||e.weekend.some((e=>!Oe(e,1,7))))throw new c("Invalid week settings");return{firstDay:e.firstDay,minimalDays:e.minimalDays,weekend:Array.from(e.weekend)}}function Oe(e,t,i){return _e(e)&&e>=t&&e<=i}function Me(e,t=2){let i;return i=e<0?"-"+(""+-e).padStart(t,"0"):(""+e).padStart(t,"0"),i}function Ue(e){return De(e)||null===e||""===e?void 0:parseInt(e,10)}function Pe(e){return De(e)||null===e||""===e?void 0:parseFloat(e)}function Ge(e){if(!De(e)&&null!==e&&""!==e){const t=1e3*parseFloat("0."+e);return Math.floor(t)}}function je(e,t,i=!1){const s=10**t;return(i?Math.trunc:Math.round)(e*s)/s}function Ve(e){return e%4==0&&(e%100!=0||e%400==0)}function Je(e){return Ve(e)?366:365}function He(e,t){const i=(s=t-1)-12*Math.floor(s/12)+1;var s;return 2===i?Ve(e+(t-i)/12)?29:28:[31,null,31,30,31,30,31,31,30,31,30,31][i-1]}function qe(e){let t=Date.UTC(e.year,e.month-1,e.day,e.hour,e.minute,e.second,e.millisecond);return e.year<100&&e.year>=0&&(t=new Date(t),t.setUTCFullYear(e.year,e.month-1,e.day)),+t}function Ye(e,t,i){return-ve(Ee(e,1,t),i)+t-1}function We(e,t=4,i=1){const s=Ye(e,t,i),r=Ye(e+1,t,i);return(Je(e)-s+r)/7}function ze(e){return e>99?e:e>de.twoDigitCutoffYear?1900+e:2e3+e}function $e(e,t,i,s=null){const r=new Date(e),n={hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"};s&&(n.timeZone=s);const a={timeZoneName:t,...n},o=new Intl.DateTimeFormat(i,a).formatToParts(r).find((e=>"timezonename"===e.type.toLowerCase()));return o?o.value:null}function Xe(e,t){let i=parseInt(e,10);Number.isNaN(i)&&(i=0);const s=parseInt(t,10)||0;return 60*i+(i<0||Object.is(i,-0)?-s:s)}function Ke(e){const t=Number(e);if("boolean"==typeof e||""===e||Number.isNaN(t))throw new c(`Invalid unit value ${e}`);return t}function Ze(e,t){const i={};for(const s in e)if(Ne(e,s)){const r=e[s];if(null==r)continue;i[t(s)]=Ke(r)}return i}function et(e,t){const i=Math.trunc(Math.abs(e/60)),s=Math.trunc(Math.abs(e%60)),r=e>=0?"+":"-";switch(t){case"short":return`${r}${Me(i,2)}:${Me(s,2)}`;case"narrow":return`${r}${i}${s>0?`:${s}`:""}`;case"techie":return`${r}${Me(i,2)}${Me(s,2)}`;default:throw new RangeError(`Value format ${t} is out of range for property format`)}}function tt(e){return function(e,t){return["hour","minute","second","millisecond"].reduce(((t,i)=>(t[i]=e[i],t)),{})}(e)}const it=["January","February","March","April","May","June","July","August","September","October","November","December"],st=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],rt=["J","F","M","A","M","J","J","A","S","O","N","D"];function nt(e){switch(e){case"narrow":return[...rt];case"short":return[...st];case"long":return[...it];case"numeric":return["1","2","3","4","5","6","7","8","9","10","11","12"];case"2-digit":return["01","02","03","04","05","06","07","08","09","10","11","12"];default:return null}}const at=["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],ot=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],ct=["M","T","W","T","F","S","S"];function lt(e){switch(e){case"narrow":return[...ct];case"short":return[...ot];case"long":return[...at];case"numeric":return["1","2","3","4","5","6","7"];default:return null}}const pt=["AM","PM"],At=["Before Christ","Anno Domini"],ut=["BC","AD"],dt=["B","A"];function ht(e){switch(e){case"narrow":return[...dt];case"short":return[...ut];case"long":return[...At];default:return null}}function mt(e,t){let i="";for(const s of e)s.literal?i+=s.val:i+=t(s.val);return i}const gt={D:d,DD:h,DDD:g,DDDD:f,t:E,tt:C,ttt:y,tttt:v,T:w,TT:I,TTT:B,TTTT:b,f:Q,ff:k,fff:_,ffff:T,F:x,FF:D,FFF:R,FFFF:F};class ft{static create(e,t={}){return new ft(e,t)}static parseFormat(e){let t=null,i="",s=!1;const r=[];for(let n=0;n0&&r.push({literal:s||/^\s+$/.test(i),val:i}),t=null,i="",s=!s):s||a===t?i+=a:(i.length>0&&r.push({literal:/^\s+$/.test(i),val:i}),i=a,t=a)}return i.length>0&&r.push({literal:s||/^\s+$/.test(i),val:i}),r}static macroTokenToFormatOpts(e){return gt[e]}constructor(e,t){this.opts=t,this.loc=e,this.systemLoc=null}formatWithSystemDefault(e,t){return null===this.systemLoc&&(this.systemLoc=this.loc.redefaultToSystem()),this.systemLoc.dtFormatter(e,{...this.opts,...t}).format()}dtFormatter(e,t={}){return this.loc.dtFormatter(e,{...this.opts,...t})}formatDateTime(e,t){return this.dtFormatter(e,t).format()}formatDateTimeParts(e,t){return this.dtFormatter(e,t).formatToParts()}formatInterval(e,t){return this.dtFormatter(e.start,t).dtf.formatRange(e.start.toJSDate(),e.end.toJSDate())}resolvedOptions(e,t){return this.dtFormatter(e,t).resolvedOptions()}num(e,t=0){if(this.opts.forceSimple)return Me(e,t);const i={...this.opts};return t>0&&(i.padTo=t),this.loc.numberFormatter(i).format(e)}formatDateTimeFromString(e,t){const i="en"===this.loc.listingMode(),s=this.loc.outputCalendar&&"gregory"!==this.loc.outputCalendar,r=(t,i)=>this.loc.extract(e,t,i),n=t=>e.isOffsetFixed&&0===e.offset&&t.allowZ?"Z":e.isValid?e.zone.formatOffset(e.ts,t.format):"",a=(t,s)=>i?function(e,t){return nt(t)[e.month-1]}(e,t):r(s?{month:t}:{month:t,day:"numeric"},"month"),o=(t,s)=>i?function(e,t){return lt(t)[e.weekday-1]}(e,t):r(s?{weekday:t}:{weekday:t,month:"long",day:"numeric"},"weekday"),c=t=>{const i=ft.macroTokenToFormatOpts(t);return i?this.formatWithSystemDefault(e,i):t},l=t=>i?function(e,t){return ht(t)[e.year<0?0:1]}(e,t):r({era:t},"era");return mt(ft.parseFormat(t),(t=>{switch(t){case"S":return this.num(e.millisecond);case"u":case"SSS":return this.num(e.millisecond,3);case"s":return this.num(e.second);case"ss":return this.num(e.second,2);case"uu":return this.num(Math.floor(e.millisecond/10),2);case"uuu":return this.num(Math.floor(e.millisecond/100));case"m":return this.num(e.minute);case"mm":return this.num(e.minute,2);case"h":return this.num(e.hour%12==0?12:e.hour%12);case"hh":return this.num(e.hour%12==0?12:e.hour%12,2);case"H":return this.num(e.hour);case"HH":return this.num(e.hour,2);case"Z":return n({format:"narrow",allowZ:this.opts.allowZ});case"ZZ":return n({format:"short",allowZ:this.opts.allowZ});case"ZZZ":return n({format:"techie",allowZ:this.opts.allowZ});case"ZZZZ":return e.zone.offsetName(e.ts,{format:"short",locale:this.loc.locale});case"ZZZZZ":return e.zone.offsetName(e.ts,{format:"long",locale:this.loc.locale});case"z":return e.zoneName;case"a":return i?function(e){return pt[e.hour<12?0:1]}(e):r({hour:"numeric",hourCycle:"h12"},"dayperiod");case"d":return s?r({day:"numeric"},"day"):this.num(e.day);case"dd":return s?r({day:"2-digit"},"day"):this.num(e.day,2);case"c":case"E":return this.num(e.weekday);case"ccc":return o("short",!0);case"cccc":return o("long",!0);case"ccccc":return o("narrow",!0);case"EEE":return o("short",!1);case"EEEE":return o("long",!1);case"EEEEE":return o("narrow",!1);case"L":return s?r({month:"numeric",day:"numeric"},"month"):this.num(e.month);case"LL":return s?r({month:"2-digit",day:"numeric"},"month"):this.num(e.month,2);case"LLL":return a("short",!0);case"LLLL":return a("long",!0);case"LLLLL":return a("narrow",!0);case"M":return s?r({month:"numeric"},"month"):this.num(e.month);case"MM":return s?r({month:"2-digit"},"month"):this.num(e.month,2);case"MMM":return a("short",!1);case"MMMM":return a("long",!1);case"MMMMM":return a("narrow",!1);case"y":return s?r({year:"numeric"},"year"):this.num(e.year);case"yy":return s?r({year:"2-digit"},"year"):this.num(e.year.toString().slice(-2),2);case"yyyy":return s?r({year:"numeric"},"year"):this.num(e.year,4);case"yyyyyy":return s?r({year:"numeric"},"year"):this.num(e.year,6);case"G":return l("short");case"GG":return l("long");case"GGGGG":return l("narrow");case"kk":return this.num(e.weekYear.toString().slice(-2),2);case"kkkk":return this.num(e.weekYear,4);case"W":return this.num(e.weekNumber);case"WW":return this.num(e.weekNumber,2);case"n":return this.num(e.localWeekNumber);case"nn":return this.num(e.localWeekNumber,2);case"ii":return this.num(e.localWeekYear.toString().slice(-2),2);case"iiii":return this.num(e.localWeekYear,4);case"o":return this.num(e.ordinal);case"ooo":return this.num(e.ordinal,3);case"q":return this.num(e.quarter);case"qq":return this.num(e.quarter,2);case"X":return this.num(Math.floor(e.ts/1e3));case"x":return this.num(e.ts);default:return c(t)}}))}formatDurationFromString(e,t){const i=e=>{switch(e[0]){case"S":return"millisecond";case"s":return"second";case"m":return"minute";case"h":return"hour";case"d":return"day";case"w":return"week";case"M":return"month";case"y":return"year";default:return null}},s=ft.parseFormat(t),r=s.reduce(((e,{literal:t,val:i})=>t?e:e.concat(i)),[]);return mt(s,(e=>t=>{const s=i(t);return s?this.num(e.get(s),t.length):t})(e.shiftTo(...r.map(i).filter((e=>e)))))}}const Et=/[A-Za-z_+-]{1,256}(?::?\/[A-Za-z0-9_+-]{1,256}(?:\/[A-Za-z0-9_+-]{1,256})?)?/;function Ct(...e){const t=e.reduce(((e,t)=>e+t.source),"");return RegExp(`^${t}$`)}function yt(...e){return t=>e.reduce((([e,i,s],r)=>{const[n,a,o]=r(t,s);return[{...e,...n},a||i,o]}),[{},null,1]).slice(0,2)}function vt(e,...t){if(null==e)return[null,null];for(const[i,s]of t){const t=i.exec(e);if(t)return s(t)}return[null,null]}function wt(...e){return(t,i)=>{const s={};let r;for(r=0;rvoid 0!==e&&(t||e&&p)?-e:e;return[{years:u(Pe(i)),months:u(Pe(s)),weeks:u(Pe(r)),days:u(Pe(n)),hours:u(Pe(a)),minutes:u(Pe(o)),seconds:u(Pe(c),"-0"===c),milliseconds:u(Ge(l),A)}]}const Mt={GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function Ut(e,t,i,s,r,n,a){const o={year:2===t.length?ze(Ue(t)):Ue(t),month:st.indexOf(i)+1,day:Ue(s),hour:Ue(r),minute:Ue(n)};return a&&(o.second=Ue(a)),e&&(o.weekday=e.length>3?at.indexOf(e)+1:ot.indexOf(e)+1),o}const Pt=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|(?:([+-]\d\d)(\d\d)))$/;function Gt(e){const[,t,i,s,r,n,a,o,c,l,p,A]=e,u=Ut(t,r,s,i,n,a,o);let d;return d=c?Mt[c]:l?0:Xe(p,A),[u,new ie(d)]}const jt=/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d\d) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d\d):(\d\d):(\d\d) GMT$/,Vt=/^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/,Jt=/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( \d|\d\d) (\d\d):(\d\d):(\d\d) (\d{4})$/;function Ht(e){const[,t,i,s,r,n,a,o]=e;return[Ut(t,r,s,i,n,a,o),ie.utcInstance]}function qt(e){const[,t,i,s,r,n,a,o]=e;return[Ut(t,o,i,s,r,n,a),ie.utcInstance]}const Yt=Ct(/([+-]\d{6}|\d{4})(?:-?(\d\d)(?:-?(\d\d))?)?/,Qt),Wt=Ct(/(\d{4})-?W(\d\d)(?:-?(\d))?/,Qt),zt=Ct(/(\d{4})-?(\d{3})/,Qt),$t=Ct(bt),Xt=yt((function(e,t){return[{year:_t(e,t),month:_t(e,t+1,1),day:_t(e,t+2,1)},null,t+3]}),Rt,Tt,Ft),Kt=yt(xt,Rt,Tt,Ft),Zt=yt(kt,Rt,Tt,Ft),ei=yt(Rt,Tt,Ft),ti=yt(Rt),ii=Ct(/(\d{4})-(\d\d)-(\d\d)/,St),si=Ct(Dt),ri=yt(Rt,Tt,Ft),ni="Invalid Duration",ai={weeks:{days:7,hours:168,minutes:10080,seconds:604800,milliseconds:6048e5},days:{hours:24,minutes:1440,seconds:86400,milliseconds:864e5},hours:{minutes:60,seconds:3600,milliseconds:36e5},minutes:{seconds:60,milliseconds:6e4},seconds:{milliseconds:1e3}},oi={years:{quarters:4,months:12,weeks:52,days:365,hours:8760,minutes:525600,seconds:31536e3,milliseconds:31536e6},quarters:{months:3,weeks:13,days:91,hours:2184,minutes:131040,seconds:7862400,milliseconds:78624e5},months:{weeks:4,days:30,hours:720,minutes:43200,seconds:2592e3,milliseconds:2592e6},...ai},ci={years:{quarters:4,months:12,weeks:52.1775,days:365.2425,hours:8765.82,minutes:525949.2,seconds:525949.2*60,milliseconds:525949.2*60*1e3},quarters:{months:3,weeks:13.044375,days:91.310625,hours:2191.455,minutes:131487.3,seconds:525949.2*60/4,milliseconds:7889237999.999999},months:{weeks:4.3481250000000005,days:30.436875,hours:730.485,minutes:43829.1,seconds:2629746,milliseconds:2629746e3},...ai},li=["years","quarters","months","weeks","days","hours","minutes","seconds","milliseconds"],pi=li.slice(0).reverse();function Ai(e,t,i=!1){const s={values:i?t.values:{...e.values,...t.values||{}},loc:e.loc.clone(t.loc),conversionAccuracy:t.conversionAccuracy||e.conversionAccuracy,matrix:t.matrix||e.matrix};return new hi(s)}function ui(e,t){var i;let s=null!=(i=t.milliseconds)?i:0;for(const i of pi.slice(1))t[i]&&(s+=t[i]*e[i].milliseconds);return s}function di(e,t){const i=ui(e,t)<0?-1:1;li.reduceRight(((s,r)=>{if(De(t[r]))return s;if(s){const n=t[s]*i,a=e[r][s],o=Math.floor(n/a);t[r]+=o*i,t[s]-=o*a*i}return r}),null),li.reduce(((i,s)=>{if(De(t[s]))return i;if(i){const r=t[i]%1;t[i]-=r,t[s]+=r*e[i][s]}return s}),null)}class hi{constructor(e){const t="longterm"===e.conversionAccuracy||!1;let i=t?ci:oi;e.matrix&&(i=e.matrix),this.values=e.values,this.loc=e.loc||ee.create(),this.conversionAccuracy=t?"longterm":"casual",this.invalid=e.invalid||null,this.matrix=i,this.isLuxonDuration=!0}static fromMillis(e,t){return hi.fromObject({milliseconds:e},t)}static fromObject(e,t={}){if(null==e||"object"!=typeof e)throw new c("Duration.fromObject: argument expected to be an object, got "+(null===e?"null":typeof e));return new hi({values:Ze(e,hi.normalizeUnit),loc:ee.fromObject(t),conversionAccuracy:t.conversionAccuracy,matrix:t.matrix})}static fromDurationLike(e){if(Se(e))return hi.fromMillis(e);if(hi.isDuration(e))return e;if("object"==typeof e)return hi.fromObject(e);throw new c(`Unknown duration argument ${e} of type ${typeof e}`)}static fromISO(e,t){const[i]=function(e){return vt(e,[Lt,Ot])}(e);return i?hi.fromObject(i,t):hi.invalid("unparsable",`the input "${e}" can't be parsed as ISO 8601`)}static fromISOTime(e,t){const[i]=function(e){return vt(e,[Nt,ti])}(e);return i?hi.fromObject(i,t):hi.invalid("unparsable",`the input "${e}" can't be parsed as ISO 8601`)}static invalid(e,t=null){if(!e)throw new c("need to specify a reason the Duration is invalid");const i=e instanceof he?e:new he(e,t);if(de.throwOnInvalid)throw new n(i);return new hi({invalid:i})}static normalizeUnit(e){const t={year:"years",years:"years",quarter:"quarters",quarters:"quarters",month:"months",months:"months",week:"weeks",weeks:"weeks",day:"days",days:"days",hour:"hours",hours:"hours",minute:"minutes",minutes:"minutes",second:"seconds",seconds:"seconds",millisecond:"milliseconds",milliseconds:"milliseconds"}[e?e.toLowerCase():e];if(!t)throw new o(e);return t}static isDuration(e){return e&&e.isLuxonDuration||!1}get locale(){return this.isValid?this.loc.locale:null}get numberingSystem(){return this.isValid?this.loc.numberingSystem:null}toFormat(e,t={}){const i={...t,floor:!1!==t.round&&!1!==t.floor};return this.isValid?ft.create(this.loc,i).formatDurationFromString(this,e):ni}toHuman(e={}){if(!this.isValid)return ni;const t=li.map((t=>{const i=this.values[t];return De(i)?null:this.loc.numberFormatter({style:"unit",unitDisplay:"long",...e,unit:t.slice(0,-1)}).format(i)})).filter((e=>e));return this.loc.listFormatter({type:"conjunction",style:e.listStyle||"narrow",...e}).format(t)}toObject(){return this.isValid?{...this.values}:{}}toISO(){if(!this.isValid)return null;let e="P";return 0!==this.years&&(e+=this.years+"Y"),0===this.months&&0===this.quarters||(e+=this.months+3*this.quarters+"M"),0!==this.weeks&&(e+=this.weeks+"W"),0!==this.days&&(e+=this.days+"D"),0===this.hours&&0===this.minutes&&0===this.seconds&&0===this.milliseconds||(e+="T"),0!==this.hours&&(e+=this.hours+"H"),0!==this.minutes&&(e+=this.minutes+"M"),0===this.seconds&&0===this.milliseconds||(e+=je(this.seconds+this.milliseconds/1e3,3)+"S"),"P"===e&&(e+="T0S"),e}toISOTime(e={}){if(!this.isValid)return null;const t=this.toMillis();return t<0||t>=864e5?null:(e={suppressMilliseconds:!1,suppressSeconds:!1,includePrefix:!1,format:"extended",...e,includeOffset:!1},as.fromMillis(t,{zone:"UTC"}).toISOTime(e))}toJSON(){return this.toISO()}toString(){return this.toISO()}[Symbol.for("nodejs.util.inspect.custom")](){return this.isValid?`Duration { values: ${JSON.stringify(this.values)} }`:`Duration { Invalid, reason: ${this.invalidReason} }`}toMillis(){return this.isValid?ui(this.matrix,this.values):NaN}valueOf(){return this.toMillis()}plus(e){if(!this.isValid)return this;const t=hi.fromDurationLike(e),i={};for(const e of li)(Ne(t.values,e)||Ne(this.values,e))&&(i[e]=t.get(e)+this.get(e));return Ai(this,{values:i},!0)}minus(e){if(!this.isValid)return this;const t=hi.fromDurationLike(e);return this.plus(t.negate())}mapUnits(e){if(!this.isValid)return this;const t={};for(const i of Object.keys(this.values))t[i]=Ke(e(this.values[i],i));return Ai(this,{values:t},!0)}get(e){return this[hi.normalizeUnit(e)]}set(e){return this.isValid?Ai(this,{values:{...this.values,...Ze(e,hi.normalizeUnit)}}):this}reconfigure({locale:e,numberingSystem:t,conversionAccuracy:i,matrix:s}={}){return Ai(this,{loc:this.loc.clone({locale:e,numberingSystem:t}),matrix:s,conversionAccuracy:i})}as(e){return this.isValid?this.shiftTo(e).get(e):NaN}normalize(){if(!this.isValid)return this;const e=this.toObject();return di(this.matrix,e),Ai(this,{values:e},!0)}rescale(){return this.isValid?Ai(this,{values:function(e){const t={};for(const[i,s]of Object.entries(e))0!==s&&(t[i]=s);return t}(this.normalize().shiftToAll().toObject())},!0):this}shiftTo(...e){if(!this.isValid)return this;if(0===e.length)return this;e=e.map((e=>hi.normalizeUnit(e)));const t={},i={},s=this.toObject();let r;for(const n of li)if(e.indexOf(n)>=0){r=n;let e=0;for(const t in i)e+=this.matrix[t][n]*i[t],i[t]=0;Se(s[n])&&(e+=s[n]);const a=Math.trunc(e);t[n]=a,i[n]=(1e3*e-1e3*a)/1e3}else Se(s[n])&&(i[n]=s[n]);for(const e in i)0!==i[e]&&(t[r]+=e===r?i[e]:i[e]/this.matrix[r][e]);return di(this.matrix,t),Ai(this,{values:t},!0)}shiftToAll(){return this.isValid?this.shiftTo("years","months","weeks","days","hours","minutes","seconds","milliseconds"):this}negate(){if(!this.isValid)return this;const e={};for(const t of Object.keys(this.values))e[t]=0===this.values[t]?0:-this.values[t];return Ai(this,{values:e},!0)}get years(){return this.isValid?this.values.years||0:NaN}get quarters(){return this.isValid?this.values.quarters||0:NaN}get months(){return this.isValid?this.values.months||0:NaN}get weeks(){return this.isValid?this.values.weeks||0:NaN}get days(){return this.isValid?this.values.days||0:NaN}get hours(){return this.isValid?this.values.hours||0:NaN}get minutes(){return this.isValid?this.values.minutes||0:NaN}get seconds(){return this.isValid?this.values.seconds||0:NaN}get milliseconds(){return this.isValid?this.values.milliseconds||0:NaN}get isValid(){return null===this.invalid}get invalidReason(){return this.invalid?this.invalid.reason:null}get invalidExplanation(){return this.invalid?this.invalid.explanation:null}equals(e){if(!this.isValid||!e.isValid)return!1;if(!this.loc.equals(e.loc))return!1;for(const s of li)if(t=this.values[s],i=e.values[s],!(void 0===t||0===t?void 0===i||0===i:t===i))return!1;var t,i;return!0}}const mi="Invalid Interval";class gi{constructor(e){this.s=e.start,this.e=e.end,this.invalid=e.invalid||null,this.isLuxonInterval=!0}static invalid(e,t=null){if(!e)throw new c("need to specify a reason the Interval is invalid");const i=e instanceof he?e:new he(e,t);if(de.throwOnInvalid)throw new r(i);return new gi({invalid:i})}static fromDateTimes(e,t){const i=os(e),s=os(t),r=function(e,t){return e&&e.isValid?t&&t.isValid?te}isBefore(e){return!!this.isValid&&this.e<=e}contains(e){return!!this.isValid&&this.s<=e&&this.e>e}set({start:e,end:t}={}){return this.isValid?gi.fromDateTimes(e||this.s,t||this.e):this}splitAt(...e){if(!this.isValid)return[];const t=e.map(os).filter((e=>this.contains(e))).sort(((e,t)=>e.toMillis()-t.toMillis())),i=[];let{s}=this,r=0;for(;s+this.e?this.e:e;i.push(gi.fromDateTimes(s,n)),s=n,r+=1}return i}splitBy(e){const t=hi.fromDurationLike(e);if(!this.isValid||!t.isValid||0===t.as("milliseconds"))return[];let i,{s}=this,r=1;const n=[];for(;se*r)));i=+e>+this.e?this.e:e,n.push(gi.fromDateTimes(s,i)),s=i,r+=1}return n}divideEqually(e){return this.isValid?this.splitBy(this.length()/e).slice(0,e):[]}overlaps(e){return this.e>e.s&&this.s=e.e}equals(e){return!(!this.isValid||!e.isValid)&&this.s.equals(e.s)&&this.e.equals(e.e)}intersection(e){if(!this.isValid)return this;const t=this.s>e.s?this.s:e.s,i=this.e=i?null:gi.fromDateTimes(t,i)}union(e){if(!this.isValid)return this;const t=this.se.e?this.e:e.e;return gi.fromDateTimes(t,i)}static merge(e){const[t,i]=e.sort(((e,t)=>e.s-t.s)).reduce((([e,t],i)=>t?t.overlaps(i)||t.abutsStart(i)?[e,t.union(i)]:[e.concat([t]),i]:[e,i]),[[],null]);return i&&t.push(i),t}static xor(e){let t=null,i=0;const s=[],r=e.map((e=>[{time:e.s,type:"s"},{time:e.e,type:"e"}])),n=Array.prototype.concat(...r).sort(((e,t)=>e.time-t.time));for(const e of n)i+="s"===e.type?1:-1,1===i?t=e.time:(t&&+t!=+e.time&&s.push(gi.fromDateTimes(t,e.time)),t=null);return gi.merge(s)}difference(...e){return gi.xor([this].concat(e)).map((e=>this.intersection(e))).filter((e=>e&&!e.isEmpty()))}toString(){return this.isValid?`[${this.s.toISO()} – ${this.e.toISO()})`:mi}[Symbol.for("nodejs.util.inspect.custom")](){return this.isValid?`Interval { start: ${this.s.toISO()}, end: ${this.e.toISO()} }`:`Interval { Invalid, reason: ${this.invalidReason} }`}toLocaleString(e=d,t={}){return this.isValid?ft.create(this.s.loc.clone(t),e).formatInterval(this):mi}toISO(e){return this.isValid?`${this.s.toISO(e)}/${this.e.toISO(e)}`:mi}toISODate(){return this.isValid?`${this.s.toISODate()}/${this.e.toISODate()}`:mi}toISOTime(e){return this.isValid?`${this.s.toISOTime(e)}/${this.e.toISOTime(e)}`:mi}toFormat(e,{separator:t=" – "}={}){return this.isValid?`${this.s.toFormat(e)}${t}${this.e.toFormat(e)}`:mi}toDuration(e,t){return this.isValid?this.e.diff(this.s,e,t):hi.invalid(this.invalidReason)}mapEndpoints(e){return gi.fromDateTimes(e(this.s),e(this.e))}}class fi{static hasDST(e=de.defaultZone){const t=as.now().setZone(e).set({month:12});return!e.isUniversal&&t.offset!==t.set({month:6}).offset}static isValidIANAZone(e){return G.isValidZone(e)}static normalizeZone(e){return re(e,de.defaultZone)}static getStartOfWeek({locale:e=null,locObj:t=null}={}){return(t||ee.create(e)).getStartOfWeek()}static getMinimumDaysInFirstWeek({locale:e=null,locObj:t=null}={}){return(t||ee.create(e)).getMinDaysInFirstWeek()}static getWeekendWeekdays({locale:e=null,locObj:t=null}={}){return(t||ee.create(e)).getWeekendDays().slice()}static months(e="long",{locale:t=null,numberingSystem:i=null,locObj:s=null,outputCalendar:r="gregory"}={}){return(s||ee.create(t,i,r)).months(e)}static monthsFormat(e="long",{locale:t=null,numberingSystem:i=null,locObj:s=null,outputCalendar:r="gregory"}={}){return(s||ee.create(t,i,r)).months(e,!0)}static weekdays(e="long",{locale:t=null,numberingSystem:i=null,locObj:s=null}={}){return(s||ee.create(t,i,null)).weekdays(e)}static weekdaysFormat(e="long",{locale:t=null,numberingSystem:i=null,locObj:s=null}={}){return(s||ee.create(t,i,null)).weekdays(e,!0)}static meridiems({locale:e=null}={}){return ee.create(e).meridiems()}static eras(e="short",{locale:t=null}={}){return ee.create(t,null,"gregory").eras(e)}static features(){return{relative:Re(),localeWeek:Te()}}}function Ei(e,t){const i=e=>e.toUTC(0,{keepLocalTime:!0}).startOf("day").valueOf(),s=i(t)-i(e);return Math.floor(hi.fromMillis(s).as("days"))}const Ci={arab:"[٠-٩]",arabext:"[۰-۹]",bali:"[᭐-᭙]",beng:"[০-৯]",deva:"[०-९]",fullwide:"[0-9]",gujr:"[૦-૯]",hanidec:"[〇|一|二|三|四|五|六|七|八|九]",khmr:"[០-៩]",knda:"[೦-೯]",laoo:"[໐-໙]",limb:"[᥆-᥏]",mlym:"[൦-൯]",mong:"[᠐-᠙]",mymr:"[၀-၉]",orya:"[୦-୯]",tamldec:"[௦-௯]",telu:"[౦-౯]",thai:"[๐-๙]",tibt:"[༠-༩]",latn:"\\d"},yi={arab:[1632,1641],arabext:[1776,1785],bali:[6992,7001],beng:[2534,2543],deva:[2406,2415],fullwide:[65296,65303],gujr:[2790,2799],khmr:[6112,6121],knda:[3302,3311],laoo:[3792,3801],limb:[6470,6479],mlym:[3430,3439],mong:[6160,6169],mymr:[4160,4169],orya:[2918,2927],tamldec:[3046,3055],telu:[3174,3183],thai:[3664,3673],tibt:[3872,3881]},vi=Ci.hanidec.replace(/[\[|\]]/g,"").split("");function wi({numberingSystem:e},t=""){return new RegExp(`${Ci[e||"latn"]}${t}`)}function Ii(e,t=(e=>e)){return{regex:e,deser:([e])=>t(function(e){let t=parseInt(e,10);if(isNaN(t)){t="";for(let i=0;i=i&&s<=r&&(t+=s-i)}}return parseInt(t,10)}return t}(e))}}const Bi=`[ ${String.fromCharCode(160)}]`,bi=new RegExp(Bi,"g");function Qi(e){return e.replace(/\./g,"\\.?").replace(bi,Bi)}function xi(e){return e.replace(/\./g,"").replace(bi," ").toLowerCase()}function ki(e,t){return null===e?null:{regex:RegExp(e.map(Qi).join("|")),deser:([i])=>e.findIndex((e=>xi(i)===xi(e)))+t}}function Di(e,t){return{regex:e,deser:([,e,t])=>Xe(e,t),groups:t}}function Si(e){return{regex:e,deser:([e])=>e}}const _i={year:{"2-digit":"yy",numeric:"yyyyy"},month:{numeric:"M","2-digit":"MM",short:"MMM",long:"MMMM"},day:{numeric:"d","2-digit":"dd"},weekday:{short:"EEE",long:"EEEE"},dayperiod:"a",dayPeriod:"a",hour12:{numeric:"h","2-digit":"hh"},hour24:{numeric:"H","2-digit":"HH"},minute:{numeric:"m","2-digit":"mm"},second:{numeric:"s","2-digit":"ss"},timeZoneName:{long:"ZZZZZ",short:"ZZZ"}};let Ri=null;function Ti(e,t){return Array.prototype.concat(...e.map((e=>function(e,t){if(e.literal)return e;const i=Ni(ft.macroTokenToFormatOpts(e.val),t);return null==i||i.includes(void 0)?e:i}(e,t))))}function Fi(e,t,i){const s=Ti(ft.parseFormat(i),e),r=s.map((t=>function(e,t){const i=wi(t),s=wi(t,"{2}"),r=wi(t,"{3}"),n=wi(t,"{4}"),a=wi(t,"{6}"),o=wi(t,"{1,2}"),c=wi(t,"{1,3}"),l=wi(t,"{1,6}"),p=wi(t,"{1,9}"),A=wi(t,"{2,4}"),u=wi(t,"{4,6}"),d=e=>{return{regex:RegExp((t=e.val,t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&"))),deser:([e])=>e,literal:!0};var t},h=(h=>{if(e.literal)return d(h);switch(h.val){case"G":return ki(t.eras("short"),0);case"GG":return ki(t.eras("long"),0);case"y":return Ii(l);case"yy":case"kk":return Ii(A,ze);case"yyyy":case"kkkk":return Ii(n);case"yyyyy":return Ii(u);case"yyyyyy":return Ii(a);case"M":case"L":case"d":case"H":case"h":case"m":case"q":case"s":case"W":return Ii(o);case"MM":case"LL":case"dd":case"HH":case"hh":case"mm":case"qq":case"ss":case"WW":return Ii(s);case"MMM":return ki(t.months("short",!0),1);case"MMMM":return ki(t.months("long",!0),1);case"LLL":return ki(t.months("short",!1),1);case"LLLL":return ki(t.months("long",!1),1);case"o":case"S":return Ii(c);case"ooo":case"SSS":return Ii(r);case"u":return Si(p);case"uu":return Si(o);case"uuu":case"E":case"c":return Ii(i);case"a":return ki(t.meridiems(),0);case"EEE":return ki(t.weekdays("short",!1),1);case"EEEE":return ki(t.weekdays("long",!1),1);case"ccc":return ki(t.weekdays("short",!0),1);case"cccc":return ki(t.weekdays("long",!0),1);case"Z":case"ZZ":return Di(new RegExp(`([+-]${o.source})(?::(${s.source}))?`),2);case"ZZZ":return Di(new RegExp(`([+-]${o.source})(${s.source})?`),2);case"z":return Si(/[a-z_+-/]{1,256}?/i);case" ":return Si(/[^\S\n\r]/);default:return d(h)}})(e)||{invalidReason:"missing Intl.DateTimeFormat.formatToParts support"};return h.token=e,h}(t,e))),n=r.find((e=>e.invalidReason));if(n)return{input:t,tokens:s,invalidReason:n.invalidReason};{const[e,i]=function(e){return[`^${e.map((e=>e.regex)).reduce(((e,t)=>`${e}(${t.source})`),"")}$`,e]}(r),n=RegExp(e,"i"),[o,c]=function(e,t,i){const s=e.match(t);if(s){const e={};let t=1;for(const r in i)if(Ne(i,r)){const n=i[r],a=n.groups?n.groups+1:1;!n.literal&&n.token&&(e[n.token.val[0]]=n.deser(s.slice(t,t+a))),t+=a}return[s,e]}return[s,{}]}(t,n,i),[l,p,A]=c?function(e){let t,i=null;return De(e.z)||(i=G.create(e.z)),De(e.Z)||(i||(i=new ie(e.Z)),t=e.Z),De(e.q)||(e.M=3*(e.q-1)+1),De(e.h)||(e.h<12&&1===e.a?e.h+=12:12===e.h&&0===e.a&&(e.h=0)),0===e.G&&e.y&&(e.y=-e.y),De(e.u)||(e.S=Ge(e.u)),[Object.keys(e).reduce(((t,i)=>{const s=(e=>{switch(e){case"S":return"millisecond";case"s":return"second";case"m":return"minute";case"h":case"H":return"hour";case"d":return"day";case"o":return"ordinal";case"L":case"M":return"month";case"y":return"year";case"E":case"c":return"weekday";case"W":return"weekNumber";case"k":return"weekYear";case"q":return"quarter";default:return null}})(i);return s&&(t[s]=e[i]),t}),{}),i,t]}(c):[null,null,void 0];if(Ne(c,"a")&&Ne(c,"H"))throw new a("Can't include meridiem when specifying 24-hour format");return{input:t,tokens:s,regex:n,rawMatches:o,matches:c,result:l,zone:p,specificOffset:A}}}function Ni(e,t){if(!e)return null;const i=ft.create(t,e).dtFormatter((Ri||(Ri=as.fromMillis(1555555555555)),Ri)),s=i.formatToParts(),r=i.resolvedOptions();return s.map((t=>function(e,t,i){const{type:s,value:r}=e;if("literal"===s){const e=/^\s+$/.test(r);return{literal:!e,val:e?" ":r}}const n=t[s];let a=s;"hour"===s&&(a=null!=t.hour12?t.hour12?"hour12":"hour24":null!=t.hourCycle?"h11"===t.hourCycle||"h12"===t.hourCycle?"hour12":"hour24":i.hour12?"hour12":"hour24");let o=_i[a];if("object"==typeof o&&(o=o[n]),o)return{literal:!1,val:o}}(t,e,r)))}const Li="Invalid DateTime",Oi=864e13;function Mi(e){return new he("unsupported zone",`the zone "${e.name}" is not supported`)}function Ui(e){return null===e.weekData&&(e.weekData=we(e.c)),e.weekData}function Pi(e){return null===e.localWeekData&&(e.localWeekData=we(e.c,e.loc.getMinDaysInFirstWeek(),e.loc.getStartOfWeek())),e.localWeekData}function Gi(e,t){const i={ts:e.ts,zone:e.zone,c:e.c,o:e.o,loc:e.loc,invalid:e.invalid};return new as({...i,...t,old:i})}function ji(e,t,i){let s=e-60*t*1e3;const r=i.offset(s);if(t===r)return[s,t];s-=60*(r-t)*1e3;const n=i.offset(s);return r===n?[s,r]:[e-60*Math.min(r,n)*1e3,Math.max(r,n)]}function Vi(e,t){const i=new Date(e+=60*t*1e3);return{year:i.getUTCFullYear(),month:i.getUTCMonth()+1,day:i.getUTCDate(),hour:i.getUTCHours(),minute:i.getUTCMinutes(),second:i.getUTCSeconds(),millisecond:i.getUTCMilliseconds()}}function Ji(e,t,i){return ji(qe(e),t,i)}function Hi(e,t){const i=e.o,s=e.c.year+Math.trunc(t.years),r=e.c.month+Math.trunc(t.months)+3*Math.trunc(t.quarters),n={...e.c,year:s,month:r,day:Math.min(e.c.day,He(s,r))+Math.trunc(t.days)+7*Math.trunc(t.weeks)},a=hi.fromObject({years:t.years-Math.trunc(t.years),quarters:t.quarters-Math.trunc(t.quarters),months:t.months-Math.trunc(t.months),weeks:t.weeks-Math.trunc(t.weeks),days:t.days-Math.trunc(t.days),hours:t.hours,minutes:t.minutes,seconds:t.seconds,milliseconds:t.milliseconds}).as("milliseconds"),o=qe(n);let[c,l]=ji(o,i,e.zone);return 0!==a&&(c+=a,l=e.zone.offset(c)),{ts:c,o:l}}function qi(e,t,i,s,r,n){const{setZone:a,zone:o}=i;if(e&&0!==Object.keys(e).length||t){const s=t||o,r=as.fromObject(e,{...i,zone:s,specificOffset:n});return a?r:r.setZone(o)}return as.invalid(new he("unparsable",`the input "${r}" can't be parsed as ${s}`))}function Yi(e,t,i=!0){return e.isValid?ft.create(ee.create("en-US"),{allowZ:i,forceSimple:!0}).formatDateTimeFromString(e,t):null}function Wi(e,t){const i=e.c.year>9999||e.c.year<0;let s="";return i&&e.c.year>=0&&(s+="+"),s+=Me(e.c.year,i?6:4),t?(s+="-",s+=Me(e.c.month),s+="-",s+=Me(e.c.day)):(s+=Me(e.c.month),s+=Me(e.c.day)),s}function zi(e,t,i,s,r,n){let a=Me(e.c.hour);return t?(a+=":",a+=Me(e.c.minute),0===e.c.millisecond&&0===e.c.second&&i||(a+=":")):a+=Me(e.c.minute),0===e.c.millisecond&&0===e.c.second&&i||(a+=Me(e.c.second),0===e.c.millisecond&&s||(a+=".",a+=Me(e.c.millisecond,3))),r&&(e.isOffsetFixed&&0===e.offset&&!n?a+="Z":e.o<0?(a+="-",a+=Me(Math.trunc(-e.o/60)),a+=":",a+=Me(Math.trunc(-e.o%60))):(a+="+",a+=Me(Math.trunc(e.o/60)),a+=":",a+=Me(Math.trunc(e.o%60)))),n&&(a+="["+e.zone.ianaName+"]"),a}const $i={month:1,day:1,hour:0,minute:0,second:0,millisecond:0},Xi={weekNumber:1,weekday:1,hour:0,minute:0,second:0,millisecond:0},Ki={ordinal:1,hour:0,minute:0,second:0,millisecond:0},Zi=["year","month","day","hour","minute","second","millisecond"],es=["weekYear","weekNumber","weekday","hour","minute","second","millisecond"],ts=["year","ordinal","hour","minute","second","millisecond"];function is(e){switch(e.toLowerCase()){case"localweekday":case"localweekdays":return"localWeekday";case"localweeknumber":case"localweeknumbers":return"localWeekNumber";case"localweekyear":case"localweekyears":return"localWeekYear";default:return function(e){const t={year:"year",years:"year",month:"month",months:"month",day:"day",days:"day",hour:"hour",hours:"hour",minute:"minute",minutes:"minute",quarter:"quarter",quarters:"quarter",second:"second",seconds:"second",millisecond:"millisecond",milliseconds:"millisecond",weekday:"weekday",weekdays:"weekday",weeknumber:"weekNumber",weeksnumber:"weekNumber",weeknumbers:"weekNumber",weekyear:"weekYear",weekyears:"weekYear",ordinal:"ordinal"}[e.toLowerCase()];if(!t)throw new o(e);return t}(e)}}function ss(e,t){const i=re(t.zone,de.defaultZone),s=ee.fromObject(t),r=de.now();let n,a;if(De(e.year))n=r;else{for(const t of Zi)De(e[t])&&(e[t]=$i[t]);const t=xe(e)||ke(e);if(t)return as.invalid(t);const s=i.offset(r);[n,a]=Ji(e,s,i)}return new as({ts:n,zone:i,loc:s,o:a})}function rs(e,t,i){const s=!!De(i.round)||i.round,r=(e,r)=>(e=je(e,s||i.calendary?0:2,!0),t.loc.clone(i).relFormatter(i).format(e,r)),n=s=>i.calendary?t.hasSame(e,s)?0:t.startOf(s).diff(e.startOf(s),s).get(s):t.diff(e,s).get(s);if(i.unit)return r(n(i.unit),i.unit);for(const e of i.units){const t=n(e);if(Math.abs(t)>=1)return r(t,e)}return r(e>t?-0:0,i.units[i.units.length-1])}function ns(e){let t,i={};return e.length>0&&"object"==typeof e[e.length-1]?(i=e[e.length-1],t=Array.from(e).slice(0,e.length-1)):t=Array.from(e),[i,t]}class as{constructor(e){const t=e.zone||de.defaultZone;let i=e.invalid||(Number.isNaN(e.ts)?new he("invalid input"):null)||(t.isValid?null:Mi(t));this.ts=De(e.ts)?de.now():e.ts;let s=null,r=null;if(!i)if(e.old&&e.old.ts===this.ts&&e.old.zone.equals(t))[s,r]=[e.old.c,e.old.o];else{const e=t.offset(this.ts);s=Vi(this.ts,e),i=Number.isNaN(s.year)?new he("invalid input"):null,s=i?null:s,r=i?null:e}this._zone=t,this.loc=e.loc||ee.create(),this.invalid=i,this.weekData=null,this.localWeekData=null,this.c=s,this.o=r,this.isLuxonDateTime=!0}static now(){return new as({})}static local(){const[e,t]=ns(arguments),[i,s,r,n,a,o,c]=t;return ss({year:i,month:s,day:r,hour:n,minute:a,second:o,millisecond:c},e)}static utc(){const[e,t]=ns(arguments),[i,s,r,n,a,o,c]=t;return e.zone=ie.utcInstance,ss({year:i,month:s,day:r,hour:n,minute:a,second:o,millisecond:c},e)}static fromJSDate(e,t={}){const i=(s=e,"[object Date]"===Object.prototype.toString.call(s)?e.valueOf():NaN);var s;if(Number.isNaN(i))return as.invalid("invalid input");const r=re(t.zone,de.defaultZone);return r.isValid?new as({ts:i,zone:r,loc:ee.fromObject(t)}):as.invalid(Mi(r))}static fromMillis(e,t={}){if(Se(e))return e<-Oi||e>Oi?as.invalid("Timestamp out of range"):new as({ts:e,zone:re(t.zone,de.defaultZone),loc:ee.fromObject(t)});throw new c(`fromMillis requires a numerical input, but received a ${typeof e} with value ${e}`)}static fromSeconds(e,t={}){if(Se(e))return new as({ts:1e3*e,zone:re(t.zone,de.defaultZone),loc:ee.fromObject(t)});throw new c("fromSeconds requires a numerical input")}static fromObject(e,t={}){e=e||{};const i=re(t.zone,de.defaultZone);if(!i.isValid)return as.invalid(Mi(i));const s=ee.fromObject(t),r=Ze(e,is),{minDaysInFirstWeek:n,startOfWeek:o}=Qe(r,s),c=de.now(),l=De(t.specificOffset)?i.offset(c):t.specificOffset,p=!De(r.ordinal),A=!De(r.year),u=!De(r.month)||!De(r.day),d=A||u,h=r.weekYear||r.weekNumber;if((d||p)&&h)throw new a("Can't mix weekYear/weekNumber units with year/month/day or ordinals");if(u&&p)throw new a("Can't mix ordinal dates with month/day");const m=h||r.weekday&&!d;let g,f,E=Vi(c,l);m?(g=es,f=Xi,E=we(E,n,o)):p?(g=ts,f=Ki,E=Be(E)):(g=Zi,f=$i);let C=!1;for(const e of g)De(r[e])?r[e]=C?f[e]:E[e]:C=!0;const y=m?function(e,t=4,i=1){const s=_e(e.weekYear),r=Oe(e.weekNumber,1,We(e.weekYear,t,i)),n=Oe(e.weekday,1,7);return s?r?!n&&fe("weekday",e.weekday):fe("week",e.weekNumber):fe("weekYear",e.weekYear)}(r,n,o):p?function(e){const t=_e(e.year),i=Oe(e.ordinal,1,Je(e.year));return t?!i&&fe("ordinal",e.ordinal):fe("year",e.year)}(r):xe(r),v=y||ke(r);if(v)return as.invalid(v);const w=m?Ie(r,n,o):p?be(r):r,[I,B]=Ji(w,l,i),b=new as({ts:I,zone:i,o:B,loc:s});return r.weekday&&d&&e.weekday!==b.weekday?as.invalid("mismatched weekday",`you can't specify both a weekday of ${r.weekday} and a date of ${b.toISO()}`):b}static fromISO(e,t={}){const[i,s]=function(e){return vt(e,[Yt,Xt],[Wt,Kt],[zt,Zt],[$t,ei])}(e);return qi(i,s,t,"ISO 8601",e)}static fromRFC2822(e,t={}){const[i,s]=function(e){return vt(function(e){return e.replace(/\([^()]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").trim()}(e),[Pt,Gt])}(e);return qi(i,s,t,"RFC 2822",e)}static fromHTTP(e,t={}){const[i,s]=function(e){return vt(e,[jt,Ht],[Vt,Ht],[Jt,qt])}(e);return qi(i,s,t,"HTTP",t)}static fromFormat(e,t,i={}){if(De(e)||De(t))throw new c("fromFormat requires an input string and a format");const{locale:s=null,numberingSystem:r=null}=i,n=ee.fromOpts({locale:s,numberingSystem:r,defaultToEN:!0}),[a,o,l,p]=function(e,t,i){const{result:s,zone:r,specificOffset:n,invalidReason:a}=Fi(e,t,i);return[s,r,n,a]}(n,e,t);return p?as.invalid(p):qi(a,o,i,`format ${t}`,e,l)}static fromString(e,t,i={}){return as.fromFormat(e,t,i)}static fromSQL(e,t={}){const[i,s]=function(e){return vt(e,[ii,Xt],[si,ri])}(e);return qi(i,s,t,"SQL",e)}static invalid(e,t=null){if(!e)throw new c("need to specify a reason the DateTime is invalid");const i=e instanceof he?e:new he(e,t);if(de.throwOnInvalid)throw new s(i);return new as({invalid:i})}static isDateTime(e){return e&&e.isLuxonDateTime||!1}static parseFormatForOpts(e,t={}){const i=Ni(e,ee.fromObject(t));return i?i.map((e=>e?e.val:null)).join(""):null}static expandFormat(e,t={}){return Ti(ft.parseFormat(e),ee.fromObject(t)).map((e=>e.val)).join("")}get(e){return this[e]}get isValid(){return null===this.invalid}get invalidReason(){return this.invalid?this.invalid.reason:null}get invalidExplanation(){return this.invalid?this.invalid.explanation:null}get locale(){return this.isValid?this.loc.locale:null}get numberingSystem(){return this.isValid?this.loc.numberingSystem:null}get outputCalendar(){return this.isValid?this.loc.outputCalendar:null}get zone(){return this._zone}get zoneName(){return this.isValid?this.zone.name:null}get year(){return this.isValid?this.c.year:NaN}get quarter(){return this.isValid?Math.ceil(this.c.month/3):NaN}get month(){return this.isValid?this.c.month:NaN}get day(){return this.isValid?this.c.day:NaN}get hour(){return this.isValid?this.c.hour:NaN}get minute(){return this.isValid?this.c.minute:NaN}get second(){return this.isValid?this.c.second:NaN}get millisecond(){return this.isValid?this.c.millisecond:NaN}get weekYear(){return this.isValid?Ui(this).weekYear:NaN}get weekNumber(){return this.isValid?Ui(this).weekNumber:NaN}get weekday(){return this.isValid?Ui(this).weekday:NaN}get isWeekend(){return this.isValid&&this.loc.getWeekendDays().includes(this.weekday)}get localWeekday(){return this.isValid?Pi(this).weekday:NaN}get localWeekNumber(){return this.isValid?Pi(this).weekNumber:NaN}get localWeekYear(){return this.isValid?Pi(this).weekYear:NaN}get ordinal(){return this.isValid?Be(this.c).ordinal:NaN}get monthShort(){return this.isValid?fi.months("short",{locObj:this.loc})[this.month-1]:null}get monthLong(){return this.isValid?fi.months("long",{locObj:this.loc})[this.month-1]:null}get weekdayShort(){return this.isValid?fi.weekdays("short",{locObj:this.loc})[this.weekday-1]:null}get weekdayLong(){return this.isValid?fi.weekdays("long",{locObj:this.loc})[this.weekday-1]:null}get offset(){return this.isValid?+this.o:NaN}get offsetNameShort(){return this.isValid?this.zone.offsetName(this.ts,{format:"short",locale:this.locale}):null}get offsetNameLong(){return this.isValid?this.zone.offsetName(this.ts,{format:"long",locale:this.locale}):null}get isOffsetFixed(){return this.isValid?this.zone.isUniversal:null}get isInDST(){return!this.isOffsetFixed&&(this.offset>this.set({month:1,day:1}).offset||this.offset>this.set({month:5}).offset)}getPossibleOffsets(){if(!this.isValid||this.isOffsetFixed)return[this];const e=864e5,t=6e4,i=qe(this.c),s=this.zone.offset(i-e),r=this.zone.offset(i+e),n=this.zone.offset(i-s*t),a=this.zone.offset(i-r*t);if(n===a)return[this];const o=i-n*t,c=i-a*t,l=Vi(o,n),p=Vi(c,a);return l.hour===p.hour&&l.minute===p.minute&&l.second===p.second&&l.millisecond===p.millisecond?[Gi(this,{ts:o}),Gi(this,{ts:c})]:[this]}get isInLeapYear(){return Ve(this.year)}get daysInMonth(){return He(this.year,this.month)}get daysInYear(){return this.isValid?Je(this.year):NaN}get weeksInWeekYear(){return this.isValid?We(this.weekYear):NaN}get weeksInLocalWeekYear(){return this.isValid?We(this.localWeekYear,this.loc.getMinDaysInFirstWeek(),this.loc.getStartOfWeek()):NaN}resolvedLocaleOptions(e={}){const{locale:t,numberingSystem:i,calendar:s}=ft.create(this.loc.clone(e),e).resolvedOptions(this);return{locale:t,numberingSystem:i,outputCalendar:s}}toUTC(e=0,t={}){return this.setZone(ie.instance(e),t)}toLocal(){return this.setZone(de.defaultZone)}setZone(e,{keepLocalTime:t=!1,keepCalendarTime:i=!1}={}){if((e=re(e,de.defaultZone)).equals(this.zone))return this;if(e.isValid){let s=this.ts;if(t||i){const t=e.offset(this.ts),i=this.toObject();[s]=Ji(i,t,e)}return Gi(this,{ts:s,zone:e})}return as.invalid(Mi(e))}reconfigure({locale:e,numberingSystem:t,outputCalendar:i}={}){return Gi(this,{loc:this.loc.clone({locale:e,numberingSystem:t,outputCalendar:i})})}setLocale(e){return this.reconfigure({locale:e})}set(e){if(!this.isValid)return this;const t=Ze(e,is),{minDaysInFirstWeek:i,startOfWeek:s}=Qe(t,this.loc),r=!De(t.weekYear)||!De(t.weekNumber)||!De(t.weekday),n=!De(t.ordinal),o=!De(t.year),c=!De(t.month)||!De(t.day),l=o||c,p=t.weekYear||t.weekNumber;if((l||n)&&p)throw new a("Can't mix weekYear/weekNumber units with year/month/day or ordinals");if(c&&n)throw new a("Can't mix ordinal dates with month/day");let A;r?A=Ie({...we(this.c,i,s),...t},i,s):De(t.ordinal)?(A={...this.toObject(),...t},De(t.day)&&(A.day=Math.min(He(A.year,A.month),A.day))):A=be({...Be(this.c),...t});const[u,d]=Ji(A,this.o,this.zone);return Gi(this,{ts:u,o:d})}plus(e){return this.isValid?Gi(this,Hi(this,hi.fromDurationLike(e))):this}minus(e){return this.isValid?Gi(this,Hi(this,hi.fromDurationLike(e).negate())):this}startOf(e,{useLocaleWeeks:t=!1}={}){if(!this.isValid)return this;const i={},s=hi.normalizeUnit(e);switch(s){case"years":i.month=1;case"quarters":case"months":i.day=1;case"weeks":case"days":i.hour=0;case"hours":i.minute=0;case"minutes":i.second=0;case"seconds":i.millisecond=0}if("weeks"===s)if(t){const e=this.loc.getStartOfWeek(),{weekday:t}=this;tthis.valueOf(),a=function(e,t,i,s){let[r,n,a,o]=function(e,t,i){const s=[["years",(e,t)=>t.year-e.year],["quarters",(e,t)=>t.quarter-e.quarter+4*(t.year-e.year)],["months",(e,t)=>t.month-e.month+12*(t.year-e.year)],["weeks",(e,t)=>{const i=Ei(e,t);return(i-i%7)/7}],["days",Ei]],r={},n=e;let a,o;for(const[c,l]of s)i.indexOf(c)>=0&&(a=c,r[c]=l(e,t),o=n.plus(r),o>t?(r[c]--,(e=n.plus(r))>t&&(o=e,r[c]--,e=n.plus(r))):e=o);return[e,r,o,a]}(e,t,i);const c=t-r,l=i.filter((e=>["hours","minutes","seconds","milliseconds"].indexOf(e)>=0));0===l.length&&(a0?hi.fromMillis(c,s).shiftTo(...l).plus(p):p}(n?this:e,n?e:this,r,s);var o;return n?a.negate():a}diffNow(e="milliseconds",t={}){return this.diff(as.now(),e,t)}until(e){return this.isValid?gi.fromDateTimes(this,e):this}hasSame(e,t,i){if(!this.isValid)return!1;const s=e.valueOf(),r=this.setZone(e.zone,{keepLocalTime:!0});return r.startOf(t,i)<=s&&s<=r.endOf(t,i)}equals(e){return this.isValid&&e.isValid&&this.valueOf()===e.valueOf()&&this.zone.equals(e.zone)&&this.loc.equals(e.loc)}toRelative(e={}){if(!this.isValid)return null;const t=e.base||as.fromObject({},{zone:this.zone}),i=e.padding?thise.valueOf()),Math.min)}static max(...e){if(!e.every(as.isDateTime))throw new c("max requires all arguments be DateTimes");return Fe(e,(e=>e.valueOf()),Math.max)}static fromFormatExplain(e,t,i={}){const{locale:s=null,numberingSystem:r=null}=i;return Fi(ee.fromOpts({locale:s,numberingSystem:r,defaultToEN:!0}),e,t)}static fromStringExplain(e,t,i={}){return as.fromFormatExplain(e,t,i)}static get DATE_SHORT(){return d}static get DATE_MED(){return h}static get DATE_MED_WITH_WEEKDAY(){return m}static get DATE_FULL(){return g}static get DATE_HUGE(){return f}static get TIME_SIMPLE(){return E}static get TIME_WITH_SECONDS(){return C}static get TIME_WITH_SHORT_OFFSET(){return y}static get TIME_WITH_LONG_OFFSET(){return v}static get TIME_24_SIMPLE(){return w}static get TIME_24_WITH_SECONDS(){return I}static get TIME_24_WITH_SHORT_OFFSET(){return B}static get TIME_24_WITH_LONG_OFFSET(){return b}static get DATETIME_SHORT(){return Q}static get DATETIME_SHORT_WITH_SECONDS(){return x}static get DATETIME_MED(){return k}static get DATETIME_MED_WITH_SECONDS(){return D}static get DATETIME_MED_WITH_WEEKDAY(){return S}static get DATETIME_FULL(){return _}static get DATETIME_FULL_WITH_SECONDS(){return R}static get DATETIME_HUGE(){return T}static get DATETIME_HUGE_WITH_SECONDS(){return F}}function os(e){if(as.isDateTime(e))return e;if(e&&e.valueOf&&Se(e.valueOf()))return as.fromJSDate(e);if(e&&"object"==typeof e)return as.fromObject(e);throw new c(`Unknown datetime argument: ${e}, of type ${typeof e}`)}t.DateTime=as,t.Duration=hi,t.FixedOffsetZone=ie,t.IANAZone=G,t.Info=fi,t.Interval=gi,t.InvalidZone=se,t.Settings=de,t.SystemZone=O,t.VERSION="3.4.4",t.Zone=N},10257:(e,t,i)=>{e.exports=i(66450)},69335:(e,t,i)=>{"use strict";var s,r,n,a=i(10257),o=i(71017).extname,c=/^\s*([^;\s]*)(?:;|\s|$)/,l=/^text\//i;function p(e){if(!e||"string"!=typeof e)return!1;var t=c.exec(e),i=t&&a[t[1].toLowerCase()];return i&&i.charset?i.charset:!(!t||!l.test(t[1]))&&"UTF-8"}t.charset=p,t.charsets={lookup:p},t.contentType=function(e){if(!e||"string"!=typeof e)return!1;var i=-1===e.indexOf("/")?t.lookup(e):e;if(!i)return!1;if(-1===i.indexOf("charset")){var s=t.charset(i);s&&(i+="; charset="+s.toLowerCase())}return i},t.extension=function(e){if(!e||"string"!=typeof e)return!1;var i=c.exec(e),s=i&&t.extensions[i[1].toLowerCase()];return!(!s||!s.length)&&s[0]},t.extensions=Object.create(null),t.lookup=function(e){if(!e||"string"!=typeof e)return!1;var i=o("x."+e).toLowerCase().substr(1);return i&&t.types[i]||!1},t.types=Object.create(null),s=t.extensions,r=t.types,n=["nginx","apache",void 0,"iana"],Object.keys(a).forEach((function(e){var t=a[e],i=t.extensions;if(i&&i.length){s[e]=i;for(var o=0;op||l===p&&"application/"===r[c].substr(0,12)))continue}r[c]=e}}}))},70474:e=>{"use strict";const t=(e,t)=>{for(const i of Reflect.ownKeys(t))Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(t,i));return e};e.exports=t,e.exports.default=t},44247:e=>{var t=1e3,i=60*t,s=60*i,r=24*s;function n(e,t,i,s){var r=t>=1.5*i;return Math.round(e/i)+" "+s+(r?"s":"")}e.exports=function(e,a){a=a||{};var o,c,l=typeof e;if("string"===l&&e.length>0)return function(e){if(!((e=String(e)).length>100)){var n=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(n){var a=parseFloat(n[1]);switch((n[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return 315576e5*a;case"weeks":case"week":case"w":return 6048e5*a;case"days":case"day":case"d":return a*r;case"hours":case"hour":case"hrs":case"hr":case"h":return a*s;case"minutes":case"minute":case"mins":case"min":case"m":return a*i;case"seconds":case"second":case"secs":case"sec":case"s":return a*t;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return a;default:return}}}}(e);if("number"===l&&isFinite(e))return a.long?(o=e,(c=Math.abs(o))>=r?n(o,c,r,"day"):c>=s?n(o,c,s,"hour"):c>=i?n(o,c,i,"minute"):c>=t?n(o,c,t,"second"):o+" ms"):function(e){var n=Math.abs(e);return n>=r?Math.round(e/r)+"d":n>=s?Math.round(e/s)+"h":n>=i?Math.round(e/i)+"m":n>=t?Math.round(e/t)+"s":e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},83733:(e,t,i)=>{var s=i(12781);function r(e){s.apply(this),e=e||{},this.writable=this.readable=!0,this.muted=!1,this.on("pipe",this._onpipe),this.replace=e.replace,this._prompt=e.prompt||null,this._hadControl=!1}function n(e){return function(){var t=this._dest,i=this._src;t&&t[e]&&t[e].apply(t,arguments),i&&i[e]&&i[e].apply(i,arguments)}}e.exports=r,r.prototype=Object.create(s.prototype),Object.defineProperty(r.prototype,"constructor",{value:r,enumerable:!1}),r.prototype.mute=function(){this.muted=!0},r.prototype.unmute=function(){this.muted=!1},Object.defineProperty(r.prototype,"_onpipe",{value:function(e){this._src=e},enumerable:!1,writable:!0,configurable:!0}),Object.defineProperty(r.prototype,"isTTY",{get:function(){return this._dest?this._dest.isTTY:!!this._src&&this._src.isTTY},set:function(e){Object.defineProperty(this,"isTTY",{value:e,enumerable:!0,writable:!0,configurable:!0})},enumerable:!0,configurable:!0}),Object.defineProperty(r.prototype,"rows",{get:function(){return this._dest?this._dest.rows:this._src?this._src.rows:void 0},enumerable:!0,configurable:!0}),Object.defineProperty(r.prototype,"columns",{get:function(){return this._dest?this._dest.columns:this._src?this._src.columns:void 0},enumerable:!0,configurable:!0}),r.prototype.pipe=function(e,t){return this._dest=e,s.prototype.pipe.call(this,e,t)},r.prototype.pause=function(){if(this._src)return this._src.pause()},r.prototype.resume=function(){if(this._src)return this._src.resume()},r.prototype.write=function(e){if(this.muted){if(!this.replace)return!0;if(e.match(/^\u001b/))return 0===e.indexOf(this._prompt)&&(e=(e=e.substr(this._prompt.length)).replace(/./g,this.replace),e=this._prompt+e),this._hadControl=!0,this.emit("data",e);this._prompt&&this._hadControl&&0===e.indexOf(this._prompt)&&(this._hadControl=!1,this.emit("data",this._prompt),e=e.substr(this._prompt.length)),e=e.toString().replace(/./g,this.replace)}this.emit("data",e)},r.prototype.end=function(e){this.muted&&(e=e&&this.replace?e.toString().replace(/./g,this.replace):null),e&&this.emit("data",e),this.emit("end")},r.prototype.destroy=n("destroy"),r.prototype.destroySoon=n("destroySoon"),r.prototype.close=n("close")},6078:(e,t,i)=>{"use strict";i.r(t),i.d(t,{App:()=>Ve,OAuthApp:()=>Je,Octokit:()=>je,RequestError:()=>g.L,createNodeMiddleware:()=>Ue});var s=i(77960);function r(e,t,i){const s="function"==typeof t?t.endpoint(i):e.request.endpoint(t,i),r="function"==typeof t?t:e.request,n=s.method,a=s.headers;let o=s.url;return{[Symbol.asyncIterator]:()=>({async next(){if(!o)return{done:!0};try{const e=function(e){if(!e.data)return{...e,data:[]};if(!("total_count"in e.data)||"url"in e.data)return e;const t=e.data.incomplete_results,i=e.data.repository_selection,s=e.data.total_count;delete e.data.incomplete_results,delete e.data.repository_selection,delete e.data.total_count;const r=Object.keys(e.data)[0],n=e.data[r];return e.data=n,void 0!==t&&(e.data.incomplete_results=t),void 0!==i&&(e.data.repository_selection=i),e.data.total_count=s,e}(await r({method:n,url:o,headers:a}));return o=((e.headers.link||"").match(/<([^>]+)>;\s*rel="next"/)||[])[1],{value:e}}catch(e){if(409!==e.status)throw e;return o="",{value:{status:200,headers:{},data:[]}}}}})}}function n(e,t,i,s){return"function"==typeof i&&(s=i,i=void 0),a(e,[],r(e,t,i)[Symbol.asyncIterator](),s)}function a(e,t,i,s){return i.next().then((r=>{if(r.done)return t;let n=!1;return t=t.concat(s?s(r.value,(function(){n=!0})):r.value.data),n?t:a(e,t,i,s)}))}var o=Object.assign(n,{iterator:r});function c(e){return{paginate:Object.assign(n.bind(null,e),{iterator:r.bind(null,e)})}}c.VERSION="6.1.2";const l=new Map;for(const[e,t]of Object.entries({actions:{addCustomLabelsToSelfHostedRunnerForOrg:["POST /orgs/{org}/actions/runners/{runner_id}/labels"],addCustomLabelsToSelfHostedRunnerForRepo:["POST /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],addSelectedRepoToRequiredWorkflow:["PUT /orgs/{org}/actions/required_workflows/{required_workflow_id}/repositories/{repository_id}"],approveWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/approve"],cancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel"],createEnvironmentVariable:["POST /repositories/{repository_id}/environments/{environment_name}/variables"],createOrUpdateEnvironmentSecret:["PUT /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}"],createOrgVariable:["POST /orgs/{org}/actions/variables"],createRegistrationTokenForOrg:["POST /orgs/{org}/actions/runners/registration-token"],createRegistrationTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/registration-token"],createRemoveTokenForOrg:["POST /orgs/{org}/actions/runners/remove-token"],createRemoveTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/remove-token"],createRepoVariable:["POST /repos/{owner}/{repo}/actions/variables"],createRequiredWorkflow:["POST /orgs/{org}/actions/required_workflows"],createWorkflowDispatch:["POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches"],deleteActionsCacheById:["DELETE /repos/{owner}/{repo}/actions/caches/{cache_id}"],deleteActionsCacheByKey:["DELETE /repos/{owner}/{repo}/actions/caches{?key,ref}"],deleteArtifact:["DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],deleteEnvironmentSecret:["DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}"],deleteEnvironmentVariable:["DELETE /repositories/{repository_id}/environments/{environment_name}/variables/{name}"],deleteOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}"],deleteOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}"],deleteRepoVariable:["DELETE /repos/{owner}/{repo}/actions/variables/{name}"],deleteRequiredWorkflow:["DELETE /orgs/{org}/actions/required_workflows/{required_workflow_id}"],deleteSelfHostedRunnerFromOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}"],deleteSelfHostedRunnerFromRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}"],deleteWorkflowRun:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}"],deleteWorkflowRunLogs:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],disableSelectedRepositoryGithubActionsOrganization:["DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}"],disableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable"],downloadArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}"],downloadJobLogsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs"],downloadWorkflowRunAttemptLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/logs"],downloadWorkflowRunLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],enableSelectedRepositoryGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories/{repository_id}"],enableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable"],generateRunnerJitconfigForOrg:["POST /orgs/{org}/actions/runners/generate-jitconfig"],generateRunnerJitconfigForRepo:["POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig"],getActionsCacheList:["GET /repos/{owner}/{repo}/actions/caches"],getActionsCacheUsage:["GET /repos/{owner}/{repo}/actions/cache/usage"],getActionsCacheUsageByRepoForOrg:["GET /orgs/{org}/actions/cache/usage-by-repository"],getActionsCacheUsageForOrg:["GET /orgs/{org}/actions/cache/usage"],getAllowedActionsOrganization:["GET /orgs/{org}/actions/permissions/selected-actions"],getAllowedActionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/selected-actions"],getArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],getEnvironmentPublicKey:["GET /repositories/{repository_id}/environments/{environment_name}/secrets/public-key"],getEnvironmentSecret:["GET /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}"],getEnvironmentVariable:["GET /repositories/{repository_id}/environments/{environment_name}/variables/{name}"],getGithubActionsDefaultWorkflowPermissionsOrganization:["GET /orgs/{org}/actions/permissions/workflow"],getGithubActionsDefaultWorkflowPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/workflow"],getGithubActionsPermissionsOrganization:["GET /orgs/{org}/actions/permissions"],getGithubActionsPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions"],getJobForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}"],getOrgPublicKey:["GET /orgs/{org}/actions/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}"],getOrgVariable:["GET /orgs/{org}/actions/variables/{name}"],getPendingDeploymentsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],getRepoPermissions:["GET /repos/{owner}/{repo}/actions/permissions",{},{renamed:["actions","getGithubActionsPermissionsRepository"]}],getRepoPublicKey:["GET /repos/{owner}/{repo}/actions/secrets/public-key"],getRepoRequiredWorkflow:["GET /repos/{org}/{repo}/actions/required_workflows/{required_workflow_id_for_repo}"],getRepoRequiredWorkflowUsage:["GET /repos/{org}/{repo}/actions/required_workflows/{required_workflow_id_for_repo}/timing"],getRepoSecret:["GET /repos/{owner}/{repo}/actions/secrets/{secret_name}"],getRepoVariable:["GET /repos/{owner}/{repo}/actions/variables/{name}"],getRequiredWorkflow:["GET /orgs/{org}/actions/required_workflows/{required_workflow_id}"],getReviewsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/approvals"],getSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}"],getSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}"],getWorkflow:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}"],getWorkflowAccessToRepository:["GET /repos/{owner}/{repo}/actions/permissions/access"],getWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}"],getWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}"],getWorkflowRunUsage:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/timing"],getWorkflowUsage:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/timing"],listArtifactsForRepo:["GET /repos/{owner}/{repo}/actions/artifacts"],listEnvironmentSecrets:["GET /repositories/{repository_id}/environments/{environment_name}/secrets"],listEnvironmentVariables:["GET /repositories/{repository_id}/environments/{environment_name}/variables"],listJobsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs"],listJobsForWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs"],listLabelsForSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}/labels"],listLabelsForSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],listOrgSecrets:["GET /orgs/{org}/actions/secrets"],listOrgVariables:["GET /orgs/{org}/actions/variables"],listRepoOrganizationSecrets:["GET /repos/{owner}/{repo}/actions/organization-secrets"],listRepoOrganizationVariables:["GET /repos/{owner}/{repo}/actions/organization-variables"],listRepoRequiredWorkflows:["GET /repos/{org}/{repo}/actions/required_workflows"],listRepoSecrets:["GET /repos/{owner}/{repo}/actions/secrets"],listRepoVariables:["GET /repos/{owner}/{repo}/actions/variables"],listRepoWorkflows:["GET /repos/{owner}/{repo}/actions/workflows"],listRequiredWorkflowRuns:["GET /repos/{owner}/{repo}/actions/required_workflows/{required_workflow_id_for_repo}/runs"],listRequiredWorkflows:["GET /orgs/{org}/actions/required_workflows"],listRunnerApplicationsForOrg:["GET /orgs/{org}/actions/runners/downloads"],listRunnerApplicationsForRepo:["GET /repos/{owner}/{repo}/actions/runners/downloads"],listSelectedReposForOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}/repositories"],listSelectedReposForOrgVariable:["GET /orgs/{org}/actions/variables/{name}/repositories"],listSelectedRepositoriesEnabledGithubActionsOrganization:["GET /orgs/{org}/actions/permissions/repositories"],listSelectedRepositoriesRequiredWorkflow:["GET /orgs/{org}/actions/required_workflows/{required_workflow_id}/repositories"],listSelfHostedRunnersForOrg:["GET /orgs/{org}/actions/runners"],listSelfHostedRunnersForRepo:["GET /repos/{owner}/{repo}/actions/runners"],listWorkflowRunArtifacts:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts"],listWorkflowRuns:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs"],listWorkflowRunsForRepo:["GET /repos/{owner}/{repo}/actions/runs"],reRunJobForWorkflowRun:["POST /repos/{owner}/{repo}/actions/jobs/{job_id}/rerun"],reRunWorkflow:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun"],reRunWorkflowFailedJobs:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs"],removeAllCustomLabelsFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels"],removeAllCustomLabelsFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],removeCustomLabelFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels/{name}"],removeCustomLabelFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels/{name}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],removeSelectedRepoFromRequiredWorkflow:["DELETE /orgs/{org}/actions/required_workflows/{required_workflow_id}/repositories/{repository_id}"],reviewCustomGatesForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule"],reviewPendingDeploymentsForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],setAllowedActionsOrganization:["PUT /orgs/{org}/actions/permissions/selected-actions"],setAllowedActionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/selected-actions"],setCustomLabelsForSelfHostedRunnerForOrg:["PUT /orgs/{org}/actions/runners/{runner_id}/labels"],setCustomLabelsForSelfHostedRunnerForRepo:["PUT /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],setGithubActionsDefaultWorkflowPermissionsOrganization:["PUT /orgs/{org}/actions/permissions/workflow"],setGithubActionsDefaultWorkflowPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/workflow"],setGithubActionsPermissionsOrganization:["PUT /orgs/{org}/actions/permissions"],setGithubActionsPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories"],setSelectedReposForOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories"],setSelectedReposToRequiredWorkflow:["PUT /orgs/{org}/actions/required_workflows/{required_workflow_id}/repositories"],setSelectedRepositoriesEnabledGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories"],setWorkflowAccessToRepository:["PUT /repos/{owner}/{repo}/actions/permissions/access"],updateEnvironmentVariable:["PATCH /repositories/{repository_id}/environments/{environment_name}/variables/{name}"],updateOrgVariable:["PATCH /orgs/{org}/actions/variables/{name}"],updateRepoVariable:["PATCH /repos/{owner}/{repo}/actions/variables/{name}"],updateRequiredWorkflow:["PATCH /orgs/{org}/actions/required_workflows/{required_workflow_id}"]},activity:{checkRepoIsStarredByAuthenticatedUser:["GET /user/starred/{owner}/{repo}"],deleteRepoSubscription:["DELETE /repos/{owner}/{repo}/subscription"],deleteThreadSubscription:["DELETE /notifications/threads/{thread_id}/subscription"],getFeeds:["GET /feeds"],getRepoSubscription:["GET /repos/{owner}/{repo}/subscription"],getThread:["GET /notifications/threads/{thread_id}"],getThreadSubscriptionForAuthenticatedUser:["GET /notifications/threads/{thread_id}/subscription"],listEventsForAuthenticatedUser:["GET /users/{username}/events"],listNotificationsForAuthenticatedUser:["GET /notifications"],listOrgEventsForAuthenticatedUser:["GET /users/{username}/events/orgs/{org}"],listPublicEvents:["GET /events"],listPublicEventsForRepoNetwork:["GET /networks/{owner}/{repo}/events"],listPublicEventsForUser:["GET /users/{username}/events/public"],listPublicOrgEvents:["GET /orgs/{org}/events"],listReceivedEventsForUser:["GET /users/{username}/received_events"],listReceivedPublicEventsForUser:["GET /users/{username}/received_events/public"],listRepoEvents:["GET /repos/{owner}/{repo}/events"],listRepoNotificationsForAuthenticatedUser:["GET /repos/{owner}/{repo}/notifications"],listReposStarredByAuthenticatedUser:["GET /user/starred"],listReposStarredByUser:["GET /users/{username}/starred"],listReposWatchedByUser:["GET /users/{username}/subscriptions"],listStargazersForRepo:["GET /repos/{owner}/{repo}/stargazers"],listWatchedReposForAuthenticatedUser:["GET /user/subscriptions"],listWatchersForRepo:["GET /repos/{owner}/{repo}/subscribers"],markNotificationsAsRead:["PUT /notifications"],markRepoNotificationsAsRead:["PUT /repos/{owner}/{repo}/notifications"],markThreadAsRead:["PATCH /notifications/threads/{thread_id}"],setRepoSubscription:["PUT /repos/{owner}/{repo}/subscription"],setThreadSubscription:["PUT /notifications/threads/{thread_id}/subscription"],starRepoForAuthenticatedUser:["PUT /user/starred/{owner}/{repo}"],unstarRepoForAuthenticatedUser:["DELETE /user/starred/{owner}/{repo}"]},apps:{addRepoToInstallation:["PUT /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","addRepoToInstallationForAuthenticatedUser"]}],addRepoToInstallationForAuthenticatedUser:["PUT /user/installations/{installation_id}/repositories/{repository_id}"],checkToken:["POST /applications/{client_id}/token"],createFromManifest:["POST /app-manifests/{code}/conversions"],createInstallationAccessToken:["POST /app/installations/{installation_id}/access_tokens"],deleteAuthorization:["DELETE /applications/{client_id}/grant"],deleteInstallation:["DELETE /app/installations/{installation_id}"],deleteToken:["DELETE /applications/{client_id}/token"],getAuthenticated:["GET /app"],getBySlug:["GET /apps/{app_slug}"],getInstallation:["GET /app/installations/{installation_id}"],getOrgInstallation:["GET /orgs/{org}/installation"],getRepoInstallation:["GET /repos/{owner}/{repo}/installation"],getSubscriptionPlanForAccount:["GET /marketplace_listing/accounts/{account_id}"],getSubscriptionPlanForAccountStubbed:["GET /marketplace_listing/stubbed/accounts/{account_id}"],getUserInstallation:["GET /users/{username}/installation"],getWebhookConfigForApp:["GET /app/hook/config"],getWebhookDelivery:["GET /app/hook/deliveries/{delivery_id}"],listAccountsForPlan:["GET /marketplace_listing/plans/{plan_id}/accounts"],listAccountsForPlanStubbed:["GET /marketplace_listing/stubbed/plans/{plan_id}/accounts"],listInstallationReposForAuthenticatedUser:["GET /user/installations/{installation_id}/repositories"],listInstallationRequestsForAuthenticatedApp:["GET /app/installation-requests"],listInstallations:["GET /app/installations"],listInstallationsForAuthenticatedUser:["GET /user/installations"],listPlans:["GET /marketplace_listing/plans"],listPlansStubbed:["GET /marketplace_listing/stubbed/plans"],listReposAccessibleToInstallation:["GET /installation/repositories"],listSubscriptionsForAuthenticatedUser:["GET /user/marketplace_purchases"],listSubscriptionsForAuthenticatedUserStubbed:["GET /user/marketplace_purchases/stubbed"],listWebhookDeliveries:["GET /app/hook/deliveries"],redeliverWebhookDelivery:["POST /app/hook/deliveries/{delivery_id}/attempts"],removeRepoFromInstallation:["DELETE /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","removeRepoFromInstallationForAuthenticatedUser"]}],removeRepoFromInstallationForAuthenticatedUser:["DELETE /user/installations/{installation_id}/repositories/{repository_id}"],resetToken:["PATCH /applications/{client_id}/token"],revokeInstallationAccessToken:["DELETE /installation/token"],scopeToken:["POST /applications/{client_id}/token/scoped"],suspendInstallation:["PUT /app/installations/{installation_id}/suspended"],unsuspendInstallation:["DELETE /app/installations/{installation_id}/suspended"],updateWebhookConfigForApp:["PATCH /app/hook/config"]},billing:{getGithubActionsBillingOrg:["GET /orgs/{org}/settings/billing/actions"],getGithubActionsBillingUser:["GET /users/{username}/settings/billing/actions"],getGithubPackagesBillingOrg:["GET /orgs/{org}/settings/billing/packages"],getGithubPackagesBillingUser:["GET /users/{username}/settings/billing/packages"],getSharedStorageBillingOrg:["GET /orgs/{org}/settings/billing/shared-storage"],getSharedStorageBillingUser:["GET /users/{username}/settings/billing/shared-storage"]},checks:{create:["POST /repos/{owner}/{repo}/check-runs"],createSuite:["POST /repos/{owner}/{repo}/check-suites"],get:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}"],getSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}"],listAnnotations:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations"],listForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-runs"],listForSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs"],listSuitesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-suites"],rerequestRun:["POST /repos/{owner}/{repo}/check-runs/{check_run_id}/rerequest"],rerequestSuite:["POST /repos/{owner}/{repo}/check-suites/{check_suite_id}/rerequest"],setSuitesPreferences:["PATCH /repos/{owner}/{repo}/check-suites/preferences"],update:["PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}"]},codeScanning:{deleteAnalysis:["DELETE /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}{?confirm_delete}"],getAlert:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}",{},{renamedParameters:{alert_id:"alert_number"}}],getAnalysis:["GET /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}"],getCodeqlDatabase:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases/{language}"],getDefaultSetup:["GET /repos/{owner}/{repo}/code-scanning/default-setup"],getSarif:["GET /repos/{owner}/{repo}/code-scanning/sarifs/{sarif_id}"],listAlertInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances"],listAlertsForOrg:["GET /orgs/{org}/code-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/code-scanning/alerts"],listAlertsInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances",{},{renamed:["codeScanning","listAlertInstances"]}],listCodeqlDatabases:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases"],listRecentAnalyses:["GET /repos/{owner}/{repo}/code-scanning/analyses"],updateAlert:["PATCH /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}"],updateDefaultSetup:["PATCH /repos/{owner}/{repo}/code-scanning/default-setup"],uploadSarif:["POST /repos/{owner}/{repo}/code-scanning/sarifs"]},codesOfConduct:{getAllCodesOfConduct:["GET /codes_of_conduct"],getConductCode:["GET /codes_of_conduct/{key}"]},codespaces:{addRepositoryForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],codespaceMachinesForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/machines"],createForAuthenticatedUser:["POST /user/codespaces"],createOrUpdateOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],createOrUpdateSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}"],createWithPrForAuthenticatedUser:["POST /repos/{owner}/{repo}/pulls/{pull_number}/codespaces"],createWithRepoForAuthenticatedUser:["POST /repos/{owner}/{repo}/codespaces"],deleteCodespacesBillingUsers:["DELETE /orgs/{org}/codespaces/billing/selected_users"],deleteForAuthenticatedUser:["DELETE /user/codespaces/{codespace_name}"],deleteFromOrganization:["DELETE /orgs/{org}/members/{username}/codespaces/{codespace_name}"],deleteOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],deleteSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}"],exportForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/exports"],getCodespacesForUserInOrg:["GET /orgs/{org}/members/{username}/codespaces"],getExportDetailsForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/exports/{export_id}"],getForAuthenticatedUser:["GET /user/codespaces/{codespace_name}"],getOrgPublicKey:["GET /orgs/{org}/codespaces/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}"],getPublicKeyForAuthenticatedUser:["GET /user/codespaces/secrets/public-key"],getRepoPublicKey:["GET /repos/{owner}/{repo}/codespaces/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],getSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}"],listDevcontainersInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/devcontainers"],listForAuthenticatedUser:["GET /user/codespaces"],listInOrganization:["GET /orgs/{org}/codespaces",{},{renamedParameters:{org_id:"org"}}],listInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces"],listOrgSecrets:["GET /orgs/{org}/codespaces/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/codespaces/secrets"],listRepositoriesForSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}/repositories"],listSecretsForAuthenticatedUser:["GET /user/codespaces/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],preFlightWithRepoForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/new"],publishForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/publish"],removeRepositoryForSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],repoMachinesForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/machines"],setCodespacesBilling:["PUT /orgs/{org}/codespaces/billing"],setCodespacesBillingUsers:["POST /orgs/{org}/codespaces/billing/selected_users"],setRepositoriesForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],startForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/start"],stopForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/stop"],stopInOrganization:["POST /orgs/{org}/members/{username}/codespaces/{codespace_name}/stop"],updateForAuthenticatedUser:["PATCH /user/codespaces/{codespace_name}"]},dependabot:{addSelectedRepoToOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],deleteOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],getAlert:["GET /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"],getOrgPublicKey:["GET /orgs/{org}/dependabot/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}"],getRepoPublicKey:["GET /repos/{owner}/{repo}/dependabot/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],listAlertsForEnterprise:["GET /enterprises/{enterprise}/dependabot/alerts"],listAlertsForOrg:["GET /orgs/{org}/dependabot/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/dependabot/alerts"],listOrgSecrets:["GET /orgs/{org}/dependabot/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/dependabot/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],updateAlert:["PATCH /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"]},dependencyGraph:{createRepositorySnapshot:["POST /repos/{owner}/{repo}/dependency-graph/snapshots"],diffRange:["GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}"],exportSbom:["GET /repos/{owner}/{repo}/dependency-graph/sbom"]},emojis:{get:["GET /emojis"]},gists:{checkIsStarred:["GET /gists/{gist_id}/star"],create:["POST /gists"],createComment:["POST /gists/{gist_id}/comments"],delete:["DELETE /gists/{gist_id}"],deleteComment:["DELETE /gists/{gist_id}/comments/{comment_id}"],fork:["POST /gists/{gist_id}/forks"],get:["GET /gists/{gist_id}"],getComment:["GET /gists/{gist_id}/comments/{comment_id}"],getRevision:["GET /gists/{gist_id}/{sha}"],list:["GET /gists"],listComments:["GET /gists/{gist_id}/comments"],listCommits:["GET /gists/{gist_id}/commits"],listForUser:["GET /users/{username}/gists"],listForks:["GET /gists/{gist_id}/forks"],listPublic:["GET /gists/public"],listStarred:["GET /gists/starred"],star:["PUT /gists/{gist_id}/star"],unstar:["DELETE /gists/{gist_id}/star"],update:["PATCH /gists/{gist_id}"],updateComment:["PATCH /gists/{gist_id}/comments/{comment_id}"]},git:{createBlob:["POST /repos/{owner}/{repo}/git/blobs"],createCommit:["POST /repos/{owner}/{repo}/git/commits"],createRef:["POST /repos/{owner}/{repo}/git/refs"],createTag:["POST /repos/{owner}/{repo}/git/tags"],createTree:["POST /repos/{owner}/{repo}/git/trees"],deleteRef:["DELETE /repos/{owner}/{repo}/git/refs/{ref}"],getBlob:["GET /repos/{owner}/{repo}/git/blobs/{file_sha}"],getCommit:["GET /repos/{owner}/{repo}/git/commits/{commit_sha}"],getRef:["GET /repos/{owner}/{repo}/git/ref/{ref}"],getTag:["GET /repos/{owner}/{repo}/git/tags/{tag_sha}"],getTree:["GET /repos/{owner}/{repo}/git/trees/{tree_sha}"],listMatchingRefs:["GET /repos/{owner}/{repo}/git/matching-refs/{ref}"],updateRef:["PATCH /repos/{owner}/{repo}/git/refs/{ref}"]},gitignore:{getAllTemplates:["GET /gitignore/templates"],getTemplate:["GET /gitignore/templates/{name}"]},interactions:{getRestrictionsForAuthenticatedUser:["GET /user/interaction-limits"],getRestrictionsForOrg:["GET /orgs/{org}/interaction-limits"],getRestrictionsForRepo:["GET /repos/{owner}/{repo}/interaction-limits"],getRestrictionsForYourPublicRepos:["GET /user/interaction-limits",{},{renamed:["interactions","getRestrictionsForAuthenticatedUser"]}],removeRestrictionsForAuthenticatedUser:["DELETE /user/interaction-limits"],removeRestrictionsForOrg:["DELETE /orgs/{org}/interaction-limits"],removeRestrictionsForRepo:["DELETE /repos/{owner}/{repo}/interaction-limits"],removeRestrictionsForYourPublicRepos:["DELETE /user/interaction-limits",{},{renamed:["interactions","removeRestrictionsForAuthenticatedUser"]}],setRestrictionsForAuthenticatedUser:["PUT /user/interaction-limits"],setRestrictionsForOrg:["PUT /orgs/{org}/interaction-limits"],setRestrictionsForRepo:["PUT /repos/{owner}/{repo}/interaction-limits"],setRestrictionsForYourPublicRepos:["PUT /user/interaction-limits",{},{renamed:["interactions","setRestrictionsForAuthenticatedUser"]}]},issues:{addAssignees:["POST /repos/{owner}/{repo}/issues/{issue_number}/assignees"],addLabels:["POST /repos/{owner}/{repo}/issues/{issue_number}/labels"],checkUserCanBeAssigned:["GET /repos/{owner}/{repo}/assignees/{assignee}"],checkUserCanBeAssignedToIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/assignees/{assignee}"],create:["POST /repos/{owner}/{repo}/issues"],createComment:["POST /repos/{owner}/{repo}/issues/{issue_number}/comments"],createLabel:["POST /repos/{owner}/{repo}/labels"],createMilestone:["POST /repos/{owner}/{repo}/milestones"],deleteComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}"],deleteLabel:["DELETE /repos/{owner}/{repo}/labels/{name}"],deleteMilestone:["DELETE /repos/{owner}/{repo}/milestones/{milestone_number}"],get:["GET /repos/{owner}/{repo}/issues/{issue_number}"],getComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}"],getEvent:["GET /repos/{owner}/{repo}/issues/events/{event_id}"],getLabel:["GET /repos/{owner}/{repo}/labels/{name}"],getMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}"],list:["GET /issues"],listAssignees:["GET /repos/{owner}/{repo}/assignees"],listComments:["GET /repos/{owner}/{repo}/issues/{issue_number}/comments"],listCommentsForRepo:["GET /repos/{owner}/{repo}/issues/comments"],listEvents:["GET /repos/{owner}/{repo}/issues/{issue_number}/events"],listEventsForRepo:["GET /repos/{owner}/{repo}/issues/events"],listEventsForTimeline:["GET /repos/{owner}/{repo}/issues/{issue_number}/timeline"],listForAuthenticatedUser:["GET /user/issues"],listForOrg:["GET /orgs/{org}/issues"],listForRepo:["GET /repos/{owner}/{repo}/issues"],listLabelsForMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels"],listLabelsForRepo:["GET /repos/{owner}/{repo}/labels"],listLabelsOnIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/labels"],listMilestones:["GET /repos/{owner}/{repo}/milestones"],lock:["PUT /repos/{owner}/{repo}/issues/{issue_number}/lock"],removeAllLabels:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels"],removeAssignees:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/assignees"],removeLabel:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}"],setLabels:["PUT /repos/{owner}/{repo}/issues/{issue_number}/labels"],unlock:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/lock"],update:["PATCH /repos/{owner}/{repo}/issues/{issue_number}"],updateComment:["PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}"],updateLabel:["PATCH /repos/{owner}/{repo}/labels/{name}"],updateMilestone:["PATCH /repos/{owner}/{repo}/milestones/{milestone_number}"]},licenses:{get:["GET /licenses/{license}"],getAllCommonlyUsed:["GET /licenses"],getForRepo:["GET /repos/{owner}/{repo}/license"]},markdown:{render:["POST /markdown"],renderRaw:["POST /markdown/raw",{headers:{"content-type":"text/plain; charset=utf-8"}}]},meta:{get:["GET /meta"],getAllVersions:["GET /versions"],getOctocat:["GET /octocat"],getZen:["GET /zen"],root:["GET /"]},migrations:{cancelImport:["DELETE /repos/{owner}/{repo}/import"],deleteArchiveForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/archive"],deleteArchiveForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/archive"],downloadArchiveForOrg:["GET /orgs/{org}/migrations/{migration_id}/archive"],getArchiveForAuthenticatedUser:["GET /user/migrations/{migration_id}/archive"],getCommitAuthors:["GET /repos/{owner}/{repo}/import/authors"],getImportStatus:["GET /repos/{owner}/{repo}/import"],getLargeFiles:["GET /repos/{owner}/{repo}/import/large_files"],getStatusForAuthenticatedUser:["GET /user/migrations/{migration_id}"],getStatusForOrg:["GET /orgs/{org}/migrations/{migration_id}"],listForAuthenticatedUser:["GET /user/migrations"],listForOrg:["GET /orgs/{org}/migrations"],listReposForAuthenticatedUser:["GET /user/migrations/{migration_id}/repositories"],listReposForOrg:["GET /orgs/{org}/migrations/{migration_id}/repositories"],listReposForUser:["GET /user/migrations/{migration_id}/repositories",{},{renamed:["migrations","listReposForAuthenticatedUser"]}],mapCommitAuthor:["PATCH /repos/{owner}/{repo}/import/authors/{author_id}"],setLfsPreference:["PATCH /repos/{owner}/{repo}/import/lfs"],startForAuthenticatedUser:["POST /user/migrations"],startForOrg:["POST /orgs/{org}/migrations"],startImport:["PUT /repos/{owner}/{repo}/import"],unlockRepoForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/repos/{repo_name}/lock"],unlockRepoForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/repos/{repo_name}/lock"],updateImport:["PATCH /repos/{owner}/{repo}/import"]},orgs:{addSecurityManagerTeam:["PUT /orgs/{org}/security-managers/teams/{team_slug}"],blockUser:["PUT /orgs/{org}/blocks/{username}"],cancelInvitation:["DELETE /orgs/{org}/invitations/{invitation_id}"],checkBlockedUser:["GET /orgs/{org}/blocks/{username}"],checkMembershipForUser:["GET /orgs/{org}/members/{username}"],checkPublicMembershipForUser:["GET /orgs/{org}/public_members/{username}"],convertMemberToOutsideCollaborator:["PUT /orgs/{org}/outside_collaborators/{username}"],createInvitation:["POST /orgs/{org}/invitations"],createWebhook:["POST /orgs/{org}/hooks"],delete:["DELETE /orgs/{org}"],deleteWebhook:["DELETE /orgs/{org}/hooks/{hook_id}"],enableOrDisableSecurityProductOnAllOrgRepos:["POST /orgs/{org}/{security_product}/{enablement}"],get:["GET /orgs/{org}"],getMembershipForAuthenticatedUser:["GET /user/memberships/orgs/{org}"],getMembershipForUser:["GET /orgs/{org}/memberships/{username}"],getWebhook:["GET /orgs/{org}/hooks/{hook_id}"],getWebhookConfigForOrg:["GET /orgs/{org}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}"],list:["GET /organizations"],listAppInstallations:["GET /orgs/{org}/installations"],listBlockedUsers:["GET /orgs/{org}/blocks"],listFailedInvitations:["GET /orgs/{org}/failed_invitations"],listForAuthenticatedUser:["GET /user/orgs"],listForUser:["GET /users/{username}/orgs"],listInvitationTeams:["GET /orgs/{org}/invitations/{invitation_id}/teams"],listMembers:["GET /orgs/{org}/members"],listMembershipsForAuthenticatedUser:["GET /user/memberships/orgs"],listOutsideCollaborators:["GET /orgs/{org}/outside_collaborators"],listPatGrantRepositories:["GET /organizations/{org}/personal-access-tokens/{pat_id}/repositories"],listPatGrantRequestRepositories:["GET /organizations/{org}/personal-access-token-requests/{pat_request_id}/repositories"],listPatGrantRequests:["GET /organizations/{org}/personal-access-token-requests"],listPatGrants:["GET /organizations/{org}/personal-access-tokens"],listPendingInvitations:["GET /orgs/{org}/invitations"],listPublicMembers:["GET /orgs/{org}/public_members"],listSecurityManagerTeams:["GET /orgs/{org}/security-managers"],listWebhookDeliveries:["GET /orgs/{org}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /orgs/{org}/hooks"],pingWebhook:["POST /orgs/{org}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeMember:["DELETE /orgs/{org}/members/{username}"],removeMembershipForUser:["DELETE /orgs/{org}/memberships/{username}"],removeOutsideCollaborator:["DELETE /orgs/{org}/outside_collaborators/{username}"],removePublicMembershipForAuthenticatedUser:["DELETE /orgs/{org}/public_members/{username}"],removeSecurityManagerTeam:["DELETE /orgs/{org}/security-managers/teams/{team_slug}"],reviewPatGrantRequest:["POST /organizations/{org}/personal-access-token-requests/{pat_request_id}"],reviewPatGrantRequestsInBulk:["POST /organizations/{org}/personal-access-token-requests"],setMembershipForUser:["PUT /orgs/{org}/memberships/{username}"],setPublicMembershipForAuthenticatedUser:["PUT /orgs/{org}/public_members/{username}"],unblockUser:["DELETE /orgs/{org}/blocks/{username}"],update:["PATCH /orgs/{org}"],updateMembershipForAuthenticatedUser:["PATCH /user/memberships/orgs/{org}"],updatePatAccess:["POST /organizations/{org}/personal-access-tokens/{pat_id}"],updatePatAccesses:["POST /organizations/{org}/personal-access-tokens"],updateWebhook:["PATCH /orgs/{org}/hooks/{hook_id}"],updateWebhookConfigForOrg:["PATCH /orgs/{org}/hooks/{hook_id}/config"]},packages:{deletePackageForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}"],deletePackageForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}"],deletePackageForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}"],deletePackageVersionForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getAllPackageVersionsForAPackageOwnedByAnOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByOrg"]}],getAllPackageVersionsForAPackageOwnedByTheAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByAuthenticatedUser"]}],getAllPackageVersionsForPackageOwnedByAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions"],getPackageForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}"],getPackageForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}"],getPackageForUser:["GET /users/{username}/packages/{package_type}/{package_name}"],getPackageVersionForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],listDockerMigrationConflictingPackagesForAuthenticatedUser:["GET /user/docker/conflicts"],listDockerMigrationConflictingPackagesForOrganization:["GET /orgs/{org}/docker/conflicts"],listDockerMigrationConflictingPackagesForUser:["GET /users/{username}/docker/conflicts"],listPackagesForAuthenticatedUser:["GET /user/packages"],listPackagesForOrganization:["GET /orgs/{org}/packages"],listPackagesForUser:["GET /users/{username}/packages"],restorePackageForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForUser:["POST /users/{username}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageVersionForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForUser:["POST /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"]},projects:{addCollaborator:["PUT /projects/{project_id}/collaborators/{username}"],createCard:["POST /projects/columns/{column_id}/cards"],createColumn:["POST /projects/{project_id}/columns"],createForAuthenticatedUser:["POST /user/projects"],createForOrg:["POST /orgs/{org}/projects"],createForRepo:["POST /repos/{owner}/{repo}/projects"],delete:["DELETE /projects/{project_id}"],deleteCard:["DELETE /projects/columns/cards/{card_id}"],deleteColumn:["DELETE /projects/columns/{column_id}"],get:["GET /projects/{project_id}"],getCard:["GET /projects/columns/cards/{card_id}"],getColumn:["GET /projects/columns/{column_id}"],getPermissionForUser:["GET /projects/{project_id}/collaborators/{username}/permission"],listCards:["GET /projects/columns/{column_id}/cards"],listCollaborators:["GET /projects/{project_id}/collaborators"],listColumns:["GET /projects/{project_id}/columns"],listForOrg:["GET /orgs/{org}/projects"],listForRepo:["GET /repos/{owner}/{repo}/projects"],listForUser:["GET /users/{username}/projects"],moveCard:["POST /projects/columns/cards/{card_id}/moves"],moveColumn:["POST /projects/columns/{column_id}/moves"],removeCollaborator:["DELETE /projects/{project_id}/collaborators/{username}"],update:["PATCH /projects/{project_id}"],updateCard:["PATCH /projects/columns/cards/{card_id}"],updateColumn:["PATCH /projects/columns/{column_id}"]},pulls:{checkIfMerged:["GET /repos/{owner}/{repo}/pulls/{pull_number}/merge"],create:["POST /repos/{owner}/{repo}/pulls"],createReplyForReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies"],createReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],createReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments"],deletePendingReview:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],deleteReviewComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}"],dismissReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/dismissals"],get:["GET /repos/{owner}/{repo}/pulls/{pull_number}"],getReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],getReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}"],list:["GET /repos/{owner}/{repo}/pulls"],listCommentsForReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments"],listCommits:["GET /repos/{owner}/{repo}/pulls/{pull_number}/commits"],listFiles:["GET /repos/{owner}/{repo}/pulls/{pull_number}/files"],listRequestedReviewers:["GET /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],listReviewComments:["GET /repos/{owner}/{repo}/pulls/{pull_number}/comments"],listReviewCommentsForRepo:["GET /repos/{owner}/{repo}/pulls/comments"],listReviews:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],merge:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge"],removeRequestedReviewers:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],requestReviewers:["POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],submitReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events"],update:["PATCH /repos/{owner}/{repo}/pulls/{pull_number}"],updateBranch:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch"],updateReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],updateReviewComment:["PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}"]},rateLimit:{get:["GET /rate_limit"]},reactions:{createForCommitComment:["POST /repos/{owner}/{repo}/comments/{comment_id}/reactions"],createForIssue:["POST /repos/{owner}/{repo}/issues/{issue_number}/reactions"],createForIssueComment:["POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],createForPullRequestReviewComment:["POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],createForRelease:["POST /repos/{owner}/{repo}/releases/{release_id}/reactions"],createForTeamDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],createForTeamDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"],deleteForCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}/reactions/{reaction_id}"],deleteForIssue:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/reactions/{reaction_id}"],deleteForIssueComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions/{reaction_id}"],deleteForPullRequestComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions/{reaction_id}"],deleteForRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}/reactions/{reaction_id}"],deleteForTeamDiscussion:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions/{reaction_id}"],deleteForTeamDiscussionComment:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions/{reaction_id}"],listForCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}/reactions"],listForIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/reactions"],listForIssueComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],listForPullRequestReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],listForRelease:["GET /repos/{owner}/{repo}/releases/{release_id}/reactions"],listForTeamDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],listForTeamDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"]},repos:{acceptInvitation:["PATCH /user/repository_invitations/{invitation_id}",{},{renamed:["repos","acceptInvitationForAuthenticatedUser"]}],acceptInvitationForAuthenticatedUser:["PATCH /user/repository_invitations/{invitation_id}"],addAppAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],addCollaborator:["PUT /repos/{owner}/{repo}/collaborators/{username}"],addStatusCheckContexts:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],addTeamAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],addUserAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],checkCollaborator:["GET /repos/{owner}/{repo}/collaborators/{username}"],checkVulnerabilityAlerts:["GET /repos/{owner}/{repo}/vulnerability-alerts"],codeownersErrors:["GET /repos/{owner}/{repo}/codeowners/errors"],compareCommits:["GET /repos/{owner}/{repo}/compare/{base}...{head}"],compareCommitsWithBasehead:["GET /repos/{owner}/{repo}/compare/{basehead}"],createAutolink:["POST /repos/{owner}/{repo}/autolinks"],createCommitComment:["POST /repos/{owner}/{repo}/commits/{commit_sha}/comments"],createCommitSignatureProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],createCommitStatus:["POST /repos/{owner}/{repo}/statuses/{sha}"],createDeployKey:["POST /repos/{owner}/{repo}/keys"],createDeployment:["POST /repos/{owner}/{repo}/deployments"],createDeploymentBranchPolicy:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],createDeploymentProtectionRule:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],createDeploymentStatus:["POST /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],createDispatchEvent:["POST /repos/{owner}/{repo}/dispatches"],createForAuthenticatedUser:["POST /user/repos"],createFork:["POST /repos/{owner}/{repo}/forks"],createInOrg:["POST /orgs/{org}/repos"],createOrUpdateEnvironment:["PUT /repos/{owner}/{repo}/environments/{environment_name}"],createOrUpdateFileContents:["PUT /repos/{owner}/{repo}/contents/{path}"],createOrgRuleset:["POST /orgs/{org}/rulesets"],createPagesDeployment:["POST /repos/{owner}/{repo}/pages/deployment"],createPagesSite:["POST /repos/{owner}/{repo}/pages"],createRelease:["POST /repos/{owner}/{repo}/releases"],createRepoRuleset:["POST /repos/{owner}/{repo}/rulesets"],createTagProtection:["POST /repos/{owner}/{repo}/tags/protection"],createUsingTemplate:["POST /repos/{template_owner}/{template_repo}/generate"],createWebhook:["POST /repos/{owner}/{repo}/hooks"],declineInvitation:["DELETE /user/repository_invitations/{invitation_id}",{},{renamed:["repos","declineInvitationForAuthenticatedUser"]}],declineInvitationForAuthenticatedUser:["DELETE /user/repository_invitations/{invitation_id}"],delete:["DELETE /repos/{owner}/{repo}"],deleteAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],deleteAdminBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],deleteAnEnvironment:["DELETE /repos/{owner}/{repo}/environments/{environment_name}"],deleteAutolink:["DELETE /repos/{owner}/{repo}/autolinks/{autolink_id}"],deleteBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection"],deleteCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}"],deleteCommitSignatureProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],deleteDeployKey:["DELETE /repos/{owner}/{repo}/keys/{key_id}"],deleteDeployment:["DELETE /repos/{owner}/{repo}/deployments/{deployment_id}"],deleteDeploymentBranchPolicy:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],deleteFile:["DELETE /repos/{owner}/{repo}/contents/{path}"],deleteInvitation:["DELETE /repos/{owner}/{repo}/invitations/{invitation_id}"],deleteOrgRuleset:["DELETE /orgs/{org}/rulesets/{ruleset_id}"],deletePagesSite:["DELETE /repos/{owner}/{repo}/pages"],deletePullRequestReviewProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],deleteRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}"],deleteReleaseAsset:["DELETE /repos/{owner}/{repo}/releases/assets/{asset_id}"],deleteRepoRuleset:["DELETE /repos/{owner}/{repo}/rulesets/{ruleset_id}"],deleteTagProtection:["DELETE /repos/{owner}/{repo}/tags/protection/{tag_protection_id}"],deleteWebhook:["DELETE /repos/{owner}/{repo}/hooks/{hook_id}"],disableAutomatedSecurityFixes:["DELETE /repos/{owner}/{repo}/automated-security-fixes"],disableDeploymentProtectionRule:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],disableLfsForRepo:["DELETE /repos/{owner}/{repo}/lfs"],disableVulnerabilityAlerts:["DELETE /repos/{owner}/{repo}/vulnerability-alerts"],downloadArchive:["GET /repos/{owner}/{repo}/zipball/{ref}",{},{renamed:["repos","downloadZipballArchive"]}],downloadTarballArchive:["GET /repos/{owner}/{repo}/tarball/{ref}"],downloadZipballArchive:["GET /repos/{owner}/{repo}/zipball/{ref}"],enableAutomatedSecurityFixes:["PUT /repos/{owner}/{repo}/automated-security-fixes"],enableLfsForRepo:["PUT /repos/{owner}/{repo}/lfs"],enableVulnerabilityAlerts:["PUT /repos/{owner}/{repo}/vulnerability-alerts"],generateReleaseNotes:["POST /repos/{owner}/{repo}/releases/generate-notes"],get:["GET /repos/{owner}/{repo}"],getAccessRestrictions:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],getAdminBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],getAllDeploymentProtectionRules:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],getAllEnvironments:["GET /repos/{owner}/{repo}/environments"],getAllStatusCheckContexts:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts"],getAllTopics:["GET /repos/{owner}/{repo}/topics"],getAppsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps"],getAutolink:["GET /repos/{owner}/{repo}/autolinks/{autolink_id}"],getBranch:["GET /repos/{owner}/{repo}/branches/{branch}"],getBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection"],getBranchRules:["GET /repos/{owner}/{repo}/rules/branches/{branch}"],getClones:["GET /repos/{owner}/{repo}/traffic/clones"],getCodeFrequencyStats:["GET /repos/{owner}/{repo}/stats/code_frequency"],getCollaboratorPermissionLevel:["GET /repos/{owner}/{repo}/collaborators/{username}/permission"],getCombinedStatusForRef:["GET /repos/{owner}/{repo}/commits/{ref}/status"],getCommit:["GET /repos/{owner}/{repo}/commits/{ref}"],getCommitActivityStats:["GET /repos/{owner}/{repo}/stats/commit_activity"],getCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}"],getCommitSignatureProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],getCommunityProfileMetrics:["GET /repos/{owner}/{repo}/community/profile"],getContent:["GET /repos/{owner}/{repo}/contents/{path}"],getContributorsStats:["GET /repos/{owner}/{repo}/stats/contributors"],getCustomDeploymentProtectionRule:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],getDeployKey:["GET /repos/{owner}/{repo}/keys/{key_id}"],getDeployment:["GET /repos/{owner}/{repo}/deployments/{deployment_id}"],getDeploymentBranchPolicy:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],getDeploymentStatus:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses/{status_id}"],getEnvironment:["GET /repos/{owner}/{repo}/environments/{environment_name}"],getLatestPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/latest"],getLatestRelease:["GET /repos/{owner}/{repo}/releases/latest"],getOrgRuleset:["GET /orgs/{org}/rulesets/{ruleset_id}"],getOrgRulesets:["GET /orgs/{org}/rulesets"],getPages:["GET /repos/{owner}/{repo}/pages"],getPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/{build_id}"],getPagesHealthCheck:["GET /repos/{owner}/{repo}/pages/health"],getParticipationStats:["GET /repos/{owner}/{repo}/stats/participation"],getPullRequestReviewProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],getPunchCardStats:["GET /repos/{owner}/{repo}/stats/punch_card"],getReadme:["GET /repos/{owner}/{repo}/readme"],getReadmeInDirectory:["GET /repos/{owner}/{repo}/readme/{dir}"],getRelease:["GET /repos/{owner}/{repo}/releases/{release_id}"],getReleaseAsset:["GET /repos/{owner}/{repo}/releases/assets/{asset_id}"],getReleaseByTag:["GET /repos/{owner}/{repo}/releases/tags/{tag}"],getRepoRuleset:["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}"],getRepoRulesets:["GET /repos/{owner}/{repo}/rulesets"],getStatusChecksProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],getTeamsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams"],getTopPaths:["GET /repos/{owner}/{repo}/traffic/popular/paths"],getTopReferrers:["GET /repos/{owner}/{repo}/traffic/popular/referrers"],getUsersWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users"],getViews:["GET /repos/{owner}/{repo}/traffic/views"],getWebhook:["GET /repos/{owner}/{repo}/hooks/{hook_id}"],getWebhookConfigForRepo:["GET /repos/{owner}/{repo}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}"],listAutolinks:["GET /repos/{owner}/{repo}/autolinks"],listBranches:["GET /repos/{owner}/{repo}/branches"],listBranchesForHeadCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/branches-where-head"],listCollaborators:["GET /repos/{owner}/{repo}/collaborators"],listCommentsForCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/comments"],listCommitCommentsForRepo:["GET /repos/{owner}/{repo}/comments"],listCommitStatusesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/statuses"],listCommits:["GET /repos/{owner}/{repo}/commits"],listContributors:["GET /repos/{owner}/{repo}/contributors"],listCustomDeploymentRuleIntegrations:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps"],listDeployKeys:["GET /repos/{owner}/{repo}/keys"],listDeploymentBranchPolicies:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],listDeploymentStatuses:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],listDeployments:["GET /repos/{owner}/{repo}/deployments"],listForAuthenticatedUser:["GET /user/repos"],listForOrg:["GET /orgs/{org}/repos"],listForUser:["GET /users/{username}/repos"],listForks:["GET /repos/{owner}/{repo}/forks"],listInvitations:["GET /repos/{owner}/{repo}/invitations"],listInvitationsForAuthenticatedUser:["GET /user/repository_invitations"],listLanguages:["GET /repos/{owner}/{repo}/languages"],listPagesBuilds:["GET /repos/{owner}/{repo}/pages/builds"],listPublic:["GET /repositories"],listPullRequestsAssociatedWithCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls"],listReleaseAssets:["GET /repos/{owner}/{repo}/releases/{release_id}/assets"],listReleases:["GET /repos/{owner}/{repo}/releases"],listTagProtection:["GET /repos/{owner}/{repo}/tags/protection"],listTags:["GET /repos/{owner}/{repo}/tags"],listTeams:["GET /repos/{owner}/{repo}/teams"],listWebhookDeliveries:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /repos/{owner}/{repo}/hooks"],merge:["POST /repos/{owner}/{repo}/merges"],mergeUpstream:["POST /repos/{owner}/{repo}/merge-upstream"],pingWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeAppAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],removeCollaborator:["DELETE /repos/{owner}/{repo}/collaborators/{username}"],removeStatusCheckContexts:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],removeStatusCheckProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],removeTeamAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],removeUserAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],renameBranch:["POST /repos/{owner}/{repo}/branches/{branch}/rename"],replaceAllTopics:["PUT /repos/{owner}/{repo}/topics"],requestPagesBuild:["POST /repos/{owner}/{repo}/pages/builds"],setAdminBranchProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],setAppAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],setStatusCheckContexts:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],setTeamAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],setUserAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],testPushWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/tests"],transfer:["POST /repos/{owner}/{repo}/transfer"],update:["PATCH /repos/{owner}/{repo}"],updateBranchProtection:["PUT /repos/{owner}/{repo}/branches/{branch}/protection"],updateCommitComment:["PATCH /repos/{owner}/{repo}/comments/{comment_id}"],updateDeploymentBranchPolicy:["PUT /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],updateInformationAboutPagesSite:["PUT /repos/{owner}/{repo}/pages"],updateInvitation:["PATCH /repos/{owner}/{repo}/invitations/{invitation_id}"],updateOrgRuleset:["PUT /orgs/{org}/rulesets/{ruleset_id}"],updatePullRequestReviewProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],updateRelease:["PATCH /repos/{owner}/{repo}/releases/{release_id}"],updateReleaseAsset:["PATCH /repos/{owner}/{repo}/releases/assets/{asset_id}"],updateRepoRuleset:["PUT /repos/{owner}/{repo}/rulesets/{ruleset_id}"],updateStatusCheckPotection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks",{},{renamed:["repos","updateStatusCheckProtection"]}],updateStatusCheckProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],updateWebhook:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}"],updateWebhookConfigForRepo:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}/config"],uploadReleaseAsset:["POST /repos/{owner}/{repo}/releases/{release_id}/assets{?name,label}",{baseUrl:"https://uploads.github.com"}]},search:{code:["GET /search/code"],commits:["GET /search/commits"],issuesAndPullRequests:["GET /search/issues"],labels:["GET /search/labels"],repos:["GET /search/repositories"],topics:["GET /search/topics"],users:["GET /search/users"]},secretScanning:{getAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"],listAlertsForEnterprise:["GET /enterprises/{enterprise}/secret-scanning/alerts"],listAlertsForOrg:["GET /orgs/{org}/secret-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/secret-scanning/alerts"],listLocationsForAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations"],updateAlert:["PATCH /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"]},securityAdvisories:{createPrivateVulnerabilityReport:["POST /repos/{owner}/{repo}/security-advisories/reports"],createRepositoryAdvisory:["POST /repos/{owner}/{repo}/security-advisories"],getRepositoryAdvisory:["GET /repos/{owner}/{repo}/security-advisories/{ghsa_id}"],listRepositoryAdvisories:["GET /repos/{owner}/{repo}/security-advisories"],updateRepositoryAdvisory:["PATCH /repos/{owner}/{repo}/security-advisories/{ghsa_id}"]},teams:{addOrUpdateMembershipForUserInOrg:["PUT /orgs/{org}/teams/{team_slug}/memberships/{username}"],addOrUpdateProjectPermissionsInOrg:["PUT /orgs/{org}/teams/{team_slug}/projects/{project_id}"],addOrUpdateRepoPermissionsInOrg:["PUT /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],checkPermissionsForProjectInOrg:["GET /orgs/{org}/teams/{team_slug}/projects/{project_id}"],checkPermissionsForRepoInOrg:["GET /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],create:["POST /orgs/{org}/teams"],createDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],createDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions"],deleteDiscussionCommentInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],deleteDiscussionInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],deleteInOrg:["DELETE /orgs/{org}/teams/{team_slug}"],getByName:["GET /orgs/{org}/teams/{team_slug}"],getDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],getDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],getMembershipForUserInOrg:["GET /orgs/{org}/teams/{team_slug}/memberships/{username}"],list:["GET /orgs/{org}/teams"],listChildInOrg:["GET /orgs/{org}/teams/{team_slug}/teams"],listDiscussionCommentsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],listDiscussionsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions"],listForAuthenticatedUser:["GET /user/teams"],listMembersInOrg:["GET /orgs/{org}/teams/{team_slug}/members"],listPendingInvitationsInOrg:["GET /orgs/{org}/teams/{team_slug}/invitations"],listProjectsInOrg:["GET /orgs/{org}/teams/{team_slug}/projects"],listReposInOrg:["GET /orgs/{org}/teams/{team_slug}/repos"],removeMembershipForUserInOrg:["DELETE /orgs/{org}/teams/{team_slug}/memberships/{username}"],removeProjectInOrg:["DELETE /orgs/{org}/teams/{team_slug}/projects/{project_id}"],removeRepoInOrg:["DELETE /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],updateDiscussionCommentInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],updateDiscussionInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],updateInOrg:["PATCH /orgs/{org}/teams/{team_slug}"]},users:{addEmailForAuthenticated:["POST /user/emails",{},{renamed:["users","addEmailForAuthenticatedUser"]}],addEmailForAuthenticatedUser:["POST /user/emails"],addSocialAccountForAuthenticatedUser:["POST /user/social_accounts"],block:["PUT /user/blocks/{username}"],checkBlocked:["GET /user/blocks/{username}"],checkFollowingForUser:["GET /users/{username}/following/{target_user}"],checkPersonIsFollowedByAuthenticated:["GET /user/following/{username}"],createGpgKeyForAuthenticated:["POST /user/gpg_keys",{},{renamed:["users","createGpgKeyForAuthenticatedUser"]}],createGpgKeyForAuthenticatedUser:["POST /user/gpg_keys"],createPublicSshKeyForAuthenticated:["POST /user/keys",{},{renamed:["users","createPublicSshKeyForAuthenticatedUser"]}],createPublicSshKeyForAuthenticatedUser:["POST /user/keys"],createSshSigningKeyForAuthenticatedUser:["POST /user/ssh_signing_keys"],deleteEmailForAuthenticated:["DELETE /user/emails",{},{renamed:["users","deleteEmailForAuthenticatedUser"]}],deleteEmailForAuthenticatedUser:["DELETE /user/emails"],deleteGpgKeyForAuthenticated:["DELETE /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","deleteGpgKeyForAuthenticatedUser"]}],deleteGpgKeyForAuthenticatedUser:["DELETE /user/gpg_keys/{gpg_key_id}"],deletePublicSshKeyForAuthenticated:["DELETE /user/keys/{key_id}",{},{renamed:["users","deletePublicSshKeyForAuthenticatedUser"]}],deletePublicSshKeyForAuthenticatedUser:["DELETE /user/keys/{key_id}"],deleteSocialAccountForAuthenticatedUser:["DELETE /user/social_accounts"],deleteSshSigningKeyForAuthenticatedUser:["DELETE /user/ssh_signing_keys/{ssh_signing_key_id}"],follow:["PUT /user/following/{username}"],getAuthenticated:["GET /user"],getByUsername:["GET /users/{username}"],getContextForUser:["GET /users/{username}/hovercard"],getGpgKeyForAuthenticated:["GET /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","getGpgKeyForAuthenticatedUser"]}],getGpgKeyForAuthenticatedUser:["GET /user/gpg_keys/{gpg_key_id}"],getPublicSshKeyForAuthenticated:["GET /user/keys/{key_id}",{},{renamed:["users","getPublicSshKeyForAuthenticatedUser"]}],getPublicSshKeyForAuthenticatedUser:["GET /user/keys/{key_id}"],getSshSigningKeyForAuthenticatedUser:["GET /user/ssh_signing_keys/{ssh_signing_key_id}"],list:["GET /users"],listBlockedByAuthenticated:["GET /user/blocks",{},{renamed:["users","listBlockedByAuthenticatedUser"]}],listBlockedByAuthenticatedUser:["GET /user/blocks"],listEmailsForAuthenticated:["GET /user/emails",{},{renamed:["users","listEmailsForAuthenticatedUser"]}],listEmailsForAuthenticatedUser:["GET /user/emails"],listFollowedByAuthenticated:["GET /user/following",{},{renamed:["users","listFollowedByAuthenticatedUser"]}],listFollowedByAuthenticatedUser:["GET /user/following"],listFollowersForAuthenticatedUser:["GET /user/followers"],listFollowersForUser:["GET /users/{username}/followers"],listFollowingForUser:["GET /users/{username}/following"],listGpgKeysForAuthenticated:["GET /user/gpg_keys",{},{renamed:["users","listGpgKeysForAuthenticatedUser"]}],listGpgKeysForAuthenticatedUser:["GET /user/gpg_keys"],listGpgKeysForUser:["GET /users/{username}/gpg_keys"],listPublicEmailsForAuthenticated:["GET /user/public_emails",{},{renamed:["users","listPublicEmailsForAuthenticatedUser"]}],listPublicEmailsForAuthenticatedUser:["GET /user/public_emails"],listPublicKeysForUser:["GET /users/{username}/keys"],listPublicSshKeysForAuthenticated:["GET /user/keys",{},{renamed:["users","listPublicSshKeysForAuthenticatedUser"]}],listPublicSshKeysForAuthenticatedUser:["GET /user/keys"],listSocialAccountsForAuthenticatedUser:["GET /user/social_accounts"],listSocialAccountsForUser:["GET /users/{username}/social_accounts"],listSshSigningKeysForAuthenticatedUser:["GET /user/ssh_signing_keys"],listSshSigningKeysForUser:["GET /users/{username}/ssh_signing_keys"],setPrimaryEmailVisibilityForAuthenticated:["PATCH /user/email/visibility",{},{renamed:["users","setPrimaryEmailVisibilityForAuthenticatedUser"]}],setPrimaryEmailVisibilityForAuthenticatedUser:["PATCH /user/email/visibility"],unblock:["DELETE /user/blocks/{username}"],unfollow:["DELETE /user/following/{username}"],updateAuthenticated:["PATCH /user"]}}))for(const[i,s]of Object.entries(t)){const[t,r,n]=s,[a,o]=t.split(/ /),c=Object.assign({method:a,url:o},r);l.has(e)||l.set(e,new Map),l.get(e).set(i,{scope:e,methodName:i,endpointDefaults:c,decorations:n})}const p={get({octokit:e,scope:t,cache:i},s){if(i[s])return i[s];const{decorations:r,endpointDefaults:n}=l.get(t).get(s);return i[s]=r?function(e,t,i,s,r){const n=e.request.defaults(s);return Object.assign((function(...s){let a=n.endpoint.merge(...s);if(r.mapToData)return a=Object.assign({},a,{data:a[r.mapToData],[r.mapToData]:void 0}),n(a);if(r.renamed){const[s,n]=r.renamed;e.log.warn(`octokit.${t}.${i}() has been renamed to octokit.${s}.${n}()`)}if(r.deprecated&&e.log.warn(r.deprecated),r.renamedParameters){const a=n.endpoint.merge(...s);for(const[s,n]of Object.entries(r.renamedParameters))s in a&&(e.log.warn(`"${s}" parameter is deprecated for "octokit.${t}.${i}()". Use "${n}" instead`),n in a||(a[n]=a[s]),delete a[s]);return n(a)}return n(...s)}),n)}(e,t,s,n,r):e.request.defaults(n),i[s]}};function A(e){const t={};for(const i of l.keys())t[i]=new Proxy({octokit:e,scope:i,cache:{}},p);return t}function u(e){return{rest:A(e)}}async function d(e,t,i,s){if(!i.request||!i.request.request)throw i;if(i.status>=400&&!e.doNotRetry.includes(i.status)){const r=null!=s.request.retries?s.request.retries:e.retries,n=Math.pow((s.request.retryCount||0)+1,2);throw t.retry.retryRequest(i,r,n)}throw i}u.VERSION="7.2.3";var h=i(32722),m=i.n(h),g=i(33755);async function f(e,t,i,s){const r=new(m());return r.on("failed",(function(t,i){const r=~~t.request.request.retries,n=~~t.request.request.retryAfter;if(s.request.retryCount=i.retryCount+1,r>i.retryCount)return n*e.retryAfterBaseValue})),r.schedule(E.bind(null,e,t,i),s)}async function E(e,t,i,s){const r=await i(i,s);return r.data&&r.data.errors&&/Something went wrong while executing your query/.test(r.data.errors[0].message)?d(e,t,new g.L(r.data.errors[0].message,500,{request:s,response:r}),s):r}function C(e,t){const i=Object.assign({enabled:!0,retryAfterBaseValue:1e3,doNotRetry:[400,401,403,404,422],retries:3},t.retry);return i.enabled&&(e.hook.error("request",d.bind(null,i,e)),e.hook.wrap("request",f.bind(null,i,e))),{retry:{retryRequest:(e,t,i)=>(e.request.request=Object.assign({},e.request.request,{retries:t,retryAfter:i}),e)}}}C.VERSION="0.0.0-development";const y=()=>Promise.resolve();function v(e,t,i){return e.retryLimiter.schedule(w,e,t,i)}async function w(e,t,i){const s="GET"!==i.method&&"HEAD"!==i.method,{pathname:r}=new URL(i.url,"http://github.test"),n="GET"===i.method&&r.startsWith("/search/"),a=r.startsWith("/graphql"),o=~~t.retryCount>0?{priority:0,weight:0}:{};e.clustering&&(o.expiration=6e4),(s||a)&&await e.write.key(e.id).schedule(o,y),s&&e.triggersNotification(r)&&await e.notifications.key(e.id).schedule(o,y),n&&await e.search.key(e.id).schedule(o,y);const c=e.global.key(e.id).schedule(o,t,i);if(a){const e=await c;if(null!=e.data.errors&&e.data.errors.some((e=>"RATE_LIMITED"===e.type)))throw Object.assign(new Error("GraphQL Rate Limit Exceeded"),{response:e,data:e.data})}return c}const I=function(e){const t=`^(?:${["/orgs/{org}/invitations","/orgs/{org}/invitations/{invitation_id}","/orgs/{org}/teams/{team_slug}/discussions","/orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments","/repos/{owner}/{repo}/collaborators/{username}","/repos/{owner}/{repo}/commits/{commit_sha}/comments","/repos/{owner}/{repo}/issues","/repos/{owner}/{repo}/issues/{issue_number}/comments","/repos/{owner}/{repo}/pulls","/repos/{owner}/{repo}/pulls/{pull_number}/comments","/repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies","/repos/{owner}/{repo}/pulls/{pull_number}/merge","/repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers","/repos/{owner}/{repo}/pulls/{pull_number}/reviews","/repos/{owner}/{repo}/releases","/teams/{team_id}/discussions","/teams/{team_id}/discussions/{discussion_number}/comments"].map((e=>e.split("/").map((e=>e.startsWith("{")?"(?:.+?)":e)).join("/"))).map((e=>`(?:${e})`)).join("|")})[^/]*$`;return new RegExp(t,"i")}(),B=I.test.bind(I),b={};function Q(e,t){const{enabled:i=!0,Bottleneck:s=m(),id:r="no-id",timeout:n=12e4,connection:a}=t.throttle||{};if(!i)return{};const o={connection:a,timeout:n};null==b.global&&function(e,t){b.global=new e.Group({id:"octokit-global",maxConcurrent:10,...t}),b.search=new e.Group({id:"octokit-search",maxConcurrent:1,minTime:2e3,...t}),b.write=new e.Group({id:"octokit-write",maxConcurrent:1,minTime:1e3,...t}),b.notifications=new e.Group({id:"octokit-notifications",maxConcurrent:1,minTime:3e3,...t})}(s,o),t.throttle&&t.throttle.minimalSecondaryRateRetryAfter&&(e.log.warn("[@octokit/plugin-throttling] `options.throttle.minimalSecondaryRateRetryAfter` is deprecated, please use `options.throttle.fallbackSecondaryRateRetryAfter` instead"),t.throttle.fallbackSecondaryRateRetryAfter=t.throttle.minimalSecondaryRateRetryAfter,delete t.throttle.minimalSecondaryRateRetryAfter),t.throttle&&t.throttle.onAbuseLimit&&(e.log.warn("[@octokit/plugin-throttling] `onAbuseLimit()` is deprecated and will be removed in a future release of `@octokit/plugin-throttling`, please use the `onSecondaryRateLimit` handler instead"),t.throttle.onSecondaryRateLimit=t.throttle.onAbuseLimit,delete t.throttle.onAbuseLimit);const c=Object.assign({clustering:null!=a,triggersNotification:B,fallbackSecondaryRateRetryAfter:60,retryAfterBaseValue:1e3,retryLimiter:new s,id:r,...b},t.throttle);if("function"!=typeof c.onSecondaryRateLimit||"function"!=typeof c.onRateLimit)throw new Error("octokit/plugin-throttling error:\n You must pass the onSecondaryRateLimit and onRateLimit error handlers.\n See https://octokit.github.io/rest.js/#throttling\n\n const octokit = new Octokit({\n throttle: {\n onSecondaryRateLimit: (retryAfter, options) => {/* ... */},\n onRateLimit: (retryAfter, options) => {/* ... */}\n }\n })\n ");const l={},p=new s.Events(l);return l.on("secondary-limit",c.onSecondaryRateLimit),l.on("rate-limit",c.onRateLimit),l.on("error",(t=>e.log.warn("Error in throttling-plugin limit handler",t))),c.retryLimiter.on("failed",(async function(t,i){const[s,r,n]=i.args,{pathname:a}=new URL(n.url,"http://github.test");if((!a.startsWith("/graphql")||401===t.status)&&403!==t.status)return;const o=~~r.retryCount;r.retryCount=o,n.request.retryCount=o;const{wantRetry:c,retryAfter:l=0}=await async function(){if(/\bsecondary rate\b/i.test(t.message)){const i=Number(t.response.headers["retry-after"])||s.fallbackSecondaryRateRetryAfter;return{wantRetry:await p.trigger("secondary-limit",i,n,e,o),retryAfter:i}}if(null!=t.response.headers&&"0"===t.response.headers["x-ratelimit-remaining"]){const i=new Date(1e3*~~t.response.headers["x-ratelimit-reset"]).getTime(),s=Math.max(Math.ceil((i-Date.now())/1e3),0);return{wantRetry:await p.trigger("rate-limit",s,n,e,o),retryAfter:s}}return{}}();return c?(r.retryCount++,l*s.retryAfterBaseValue):void 0})),e.hook.wrap("request",v.bind(null,c)),{}}Q.VERSION="5.2.3",Q.triggersNotification=B;var x=i(21375),k=i(85111),D=i(8518),S=i(57751);function _(e){const t=new ArrayBuffer(e.length),i=new Uint8Array(t);for(let t=0,s=e.length;t{if(/BEGIN RSA PRIVATE KEY/.test(e))throw new Error("[universal-github-app-jwt] Private Key is in PKCS#1 format, but only PKCS#8 is supported. See https://github.com/gr2m/universal-github-app-jwt#readme");const i={name:"RSASSA-PKCS1-v1_5",hash:{name:"SHA-256"}},s=function(e){const t=e.trim().split("\n").slice(1,-1).join("");return _(atob(t))}(e),r=await crypto.subtle.importKey("pkcs8",s,i,!1,["sign"]),n=function(e,t){return`${T({alg:"RS256",typ:"JWT"})}.${T(t)}`}(0,t),a=_(n);return`${n}.${function(e){for(var t="",i=new Uint8Array(e),s=i.byteLength,r=0;r{"function"==typeof O.emitWarning?O.emitWarning(e,t,i,s):console.error(`[${i}] ${t}: ${e}`)};let U=globalThis.AbortController,P=globalThis.AbortSignal;if(void 0===U){P=class{onabort;_onabort=[];reason;aborted=!1;addEventListener(e,t){this._onabort.push(t)}},U=class{constructor(){t()}signal=new P;abort(e){if(!this.signal.aborted){this.signal.reason=e,this.signal.aborted=!0;for(const t of this.signal._onabort)t(e);this.signal.onabort?.(e)}}};let e="1"!==O.env?.LRU_CACHE_IGNORE_AC_WARNING;const t=()=>{e&&(e=!1,M("AbortController is not defined. If using lru-cache in node 14, load an AbortController polyfill from the `node-abort-controller` package. A minimal polyfill is provided for use by LRUCache.fetch(), but it should not be relied upon in other contexts (eg, passing it to other APIs that use AbortController/AbortSignal might have undesirable effects). You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.","NO_ABORT_CONTROLLER","ENOTSUP",t))}}Symbol("type");const G=e=>e&&e===Math.floor(e)&&e>0&&isFinite(e),j=e=>G(e)?e<=Math.pow(2,8)?Uint8Array:e<=Math.pow(2,16)?Uint16Array:e<=Math.pow(2,32)?Uint32Array:e<=Number.MAX_SAFE_INTEGER?V:null:null;class V extends Array{constructor(e){super(e),this.fill(0)}}class J{heap;length;static#e=!1;static create(e){const t=j(e);if(!t)return[];J.#e=!0;const i=new J(e,t);return J.#e=!1,i}constructor(e,t){if(!J.#e)throw new TypeError("instantiate Stack using Stack.create(n)");this.heap=new t(e),this.length=0}push(e){this.heap[this.length++]=e}pop(){return this.heap[--this.length]}}class H{#t;#i;#s;#r;#n;ttl;ttlResolution;ttlAutopurge;updateAgeOnGet;updateAgeOnHas;allowStale;noDisposeOnSet;noUpdateTTL;maxEntrySize;sizeCalculation;noDeleteOnFetchRejection;noDeleteOnStaleGet;allowStaleOnFetchAbort;allowStaleOnFetchRejection;ignoreFetchAbort;#a;#o;#c;#l;#p;#A;#u;#d;#h;#m;#g;#f;#E;#C;#y;#v;#w;static unsafeExposeInternals(e){return{starts:e.#E,ttls:e.#C,sizes:e.#f,keyMap:e.#c,keyList:e.#l,valList:e.#p,next:e.#A,prev:e.#u,get head(){return e.#d},get tail(){return e.#h},free:e.#m,isBackgroundFetch:t=>e.#I(t),backgroundFetch:(t,i,s,r)=>e.#B(t,i,s,r),moveToTail:t=>e.#b(t),indexes:t=>e.#Q(t),rindexes:t=>e.#x(t),isStale:t=>e.#k(t)}}get max(){return this.#t}get maxSize(){return this.#i}get calculatedSize(){return this.#o}get size(){return this.#a}get fetchMethod(){return this.#n}get dispose(){return this.#s}get disposeAfter(){return this.#r}constructor(e){const{max:t=0,ttl:i,ttlResolution:s=1,ttlAutopurge:r,updateAgeOnGet:n,updateAgeOnHas:a,allowStale:o,dispose:c,disposeAfter:l,noDisposeOnSet:p,noUpdateTTL:A,maxSize:u=0,maxEntrySize:d=0,sizeCalculation:h,fetchMethod:m,noDeleteOnFetchRejection:g,noDeleteOnStaleGet:f,allowStaleOnFetchRejection:E,allowStaleOnFetchAbort:C,ignoreFetchAbort:y}=e;if(0!==t&&!G(t))throw new TypeError("max option must be a nonnegative integer");const v=t?j(t):Array;if(!v)throw new Error("invalid max value: "+t);if(this.#t=t,this.#i=u,this.maxEntrySize=d||this.#i,this.sizeCalculation=h,this.sizeCalculation){if(!this.#i&&!this.maxEntrySize)throw new TypeError("cannot set sizeCalculation without setting maxSize or maxEntrySize");if("function"!=typeof this.sizeCalculation)throw new TypeError("sizeCalculation set to non-function")}if(void 0!==m&&"function"!=typeof m)throw new TypeError("fetchMethod must be a function if specified");if(this.#n=m,this.#v=!!m,this.#c=new Map,this.#l=new Array(t).fill(void 0),this.#p=new Array(t).fill(void 0),this.#A=new v(t),this.#u=new v(t),this.#d=0,this.#h=0,this.#m=J.create(t),this.#a=0,this.#o=0,"function"==typeof c&&(this.#s=c),"function"==typeof l?(this.#r=l,this.#g=[]):(this.#r=void 0,this.#g=void 0),this.#y=!!this.#s,this.#w=!!this.#r,this.noDisposeOnSet=!!p,this.noUpdateTTL=!!A,this.noDeleteOnFetchRejection=!!g,this.allowStaleOnFetchRejection=!!E,this.allowStaleOnFetchAbort=!!C,this.ignoreFetchAbort=!!y,0!==this.maxEntrySize){if(0!==this.#i&&!G(this.#i))throw new TypeError("maxSize must be a positive integer if specified");if(!G(this.maxEntrySize))throw new TypeError("maxEntrySize must be a positive integer if specified");this.#D()}if(this.allowStale=!!o,this.noDeleteOnStaleGet=!!f,this.updateAgeOnGet=!!n,this.updateAgeOnHas=!!a,this.ttlResolution=G(s)||0===s?s:1,this.ttlAutopurge=!!r,this.ttl=i||0,this.ttl){if(!G(this.ttl))throw new TypeError("ttl must be a positive integer if specified");this.#S()}if(0===this.#t&&0===this.ttl&&0===this.#i)throw new TypeError("At least one of max, maxSize, or ttl is required");if(!this.ttlAutopurge&&!this.#t&&!this.#i){const e="LRU_CACHE_UNBOUNDED";(e=>!L.has(e))(e)&&(L.add(e),M("TTL caching without ttlAutopurge, max, or maxSize can result in unbounded memory consumption.","UnboundedCacheWarning",e,H))}}getRemainingTTL(e){return this.#c.has(e)?1/0:0}#S(){const e=new V(this.#t),t=new V(this.#t);this.#C=e,this.#E=t,this.#_=(i,s,r=N.now())=>{if(t[i]=0!==s?r:0,e[i]=s,0!==s&&this.ttlAutopurge){const e=setTimeout((()=>{this.#k(i)&&this.delete(this.#l[i])}),s+1);e.unref&&e.unref()}},this.#R=i=>{t[i]=0!==e[i]?N.now():0},this.#T=(r,n)=>{if(e[n]){const a=e[n],o=t[n];r.ttl=a,r.start=o,r.now=i||s();const c=r.now-o;r.remainingTTL=a-c}};let i=0;const s=()=>{const e=N.now();if(this.ttlResolution>0){i=e;const t=setTimeout((()=>i=0),this.ttlResolution);t.unref&&t.unref()}return e};this.getRemainingTTL=r=>{const n=this.#c.get(r);if(void 0===n)return 0;const a=e[n],o=t[n];return 0===a||0===o?1/0:a-((i||s())-o)},this.#k=r=>0!==e[r]&&0!==t[r]&&(i||s())-t[r]>e[r]}#R=()=>{};#T=()=>{};#_=()=>{};#k=()=>!1;#D(){const e=new V(this.#t);this.#o=0,this.#f=e,this.#F=t=>{this.#o-=e[t],e[t]=0},this.#N=(e,t,i,s)=>{if(this.#I(t))return 0;if(!G(i)){if(!s)throw new TypeError("invalid size value (must be positive integer). When maxSize or maxEntrySize is used, sizeCalculation or size must be set.");if("function"!=typeof s)throw new TypeError("sizeCalculation must be a function");if(i=s(t,e),!G(i))throw new TypeError("sizeCalculation return invalid (expect positive integer)")}return i},this.#L=(t,i,s)=>{if(e[t]=i,this.#i){const i=this.#i-e[t];for(;this.#o>i;)this.#O(!0)}this.#o+=e[t],s&&(s.entrySize=i,s.totalCalculatedSize=this.#o)}}#F=e=>{};#L=(e,t,i)=>{};#N=(e,t,i,s)=>{if(i||s)throw new TypeError("cannot set size without setting maxSize or maxEntrySize on cache");return 0};*#Q({allowStale:e=this.allowStale}={}){if(this.#a)for(let t=this.#h;this.#M(t)&&(!e&&this.#k(t)||(yield t),t!==this.#d);)t=this.#u[t]}*#x({allowStale:e=this.allowStale}={}){if(this.#a)for(let t=this.#d;this.#M(t)&&(!e&&this.#k(t)||(yield t),t!==this.#h);)t=this.#A[t]}#M(e){return void 0!==e&&this.#c.get(this.#l[e])===e}*entries(){for(const e of this.#Q())void 0===this.#p[e]||void 0===this.#l[e]||this.#I(this.#p[e])||(yield[this.#l[e],this.#p[e]])}*rentries(){for(const e of this.#x())void 0===this.#p[e]||void 0===this.#l[e]||this.#I(this.#p[e])||(yield[this.#l[e],this.#p[e]])}*keys(){for(const e of this.#Q()){const t=this.#l[e];void 0===t||this.#I(this.#p[e])||(yield t)}}*rkeys(){for(const e of this.#x()){const t=this.#l[e];void 0===t||this.#I(this.#p[e])||(yield t)}}*values(){for(const e of this.#Q())void 0===this.#p[e]||this.#I(this.#p[e])||(yield this.#p[e])}*rvalues(){for(const e of this.#x())void 0===this.#p[e]||this.#I(this.#p[e])||(yield this.#p[e])}[Symbol.iterator](){return this.entries()}find(e,t={}){for(const i of this.#Q()){const s=this.#p[i],r=this.#I(s)?s.__staleWhileFetching:s;if(void 0!==r&&e(r,this.#l[i],this))return this.get(this.#l[i],t)}}forEach(e,t=this){for(const i of this.#Q()){const s=this.#p[i],r=this.#I(s)?s.__staleWhileFetching:s;void 0!==r&&e.call(t,r,this.#l[i],this)}}rforEach(e,t=this){for(const i of this.#x()){const s=this.#p[i],r=this.#I(s)?s.__staleWhileFetching:s;void 0!==r&&e.call(t,r,this.#l[i],this)}}purgeStale(){let e=!1;for(const t of this.#x({allowStale:!0}))this.#k(t)&&(this.delete(this.#l[t]),e=!0);return e}dump(){const e=[];for(const t of this.#Q({allowStale:!0})){const i=this.#l[t],s=this.#p[t],r=this.#I(s)?s.__staleWhileFetching:s;if(void 0===r||void 0===i)continue;const n={value:r};if(this.#C&&this.#E){n.ttl=this.#C[t];const e=N.now()-this.#E[t];n.start=Math.floor(Date.now()-e)}this.#f&&(n.size=this.#f[t]),e.unshift([i,n])}return e}load(e){this.clear();for(const[t,i]of e){if(i.start){const e=Date.now()-i.start;i.start=N.now()-e}this.set(t,i.value,i)}}set(e,t,i={}){if(void 0===t)return this.delete(e),this;const{ttl:s=this.ttl,start:r,noDisposeOnSet:n=this.noDisposeOnSet,sizeCalculation:a=this.sizeCalculation,status:o}=i;let{noUpdateTTL:c=this.noUpdateTTL}=i;const l=this.#N(e,t,i.size||0,a);if(this.maxEntrySize&&l>this.maxEntrySize)return o&&(o.set="miss",o.maxEntrySizeExceeded=!0),this.delete(e),this;let p=0===this.#a?void 0:this.#c.get(e);if(void 0===p)p=0===this.#a?this.#h:0!==this.#m.length?this.#m.pop():this.#a===this.#t?this.#O(!1):this.#a,this.#l[p]=e,this.#p[p]=t,this.#c.set(e,p),this.#A[this.#h]=p,this.#u[p]=this.#h,this.#h=p,this.#a++,this.#L(p,l,o),o&&(o.set="add"),c=!1;else{this.#b(p);const i=this.#p[p];if(t!==i){if(this.#v&&this.#I(i)?i.__abortController.abort(new Error("replaced")):n||(this.#y&&this.#s?.(i,e,"set"),this.#w&&this.#g?.push([i,e,"set"])),this.#F(p),this.#L(p,l,o),this.#p[p]=t,o){o.set="replace";const e=i&&this.#I(i)?i.__staleWhileFetching:i;void 0!==e&&(o.oldValue=e)}}else o&&(o.set="update")}if(0===s||this.#C||this.#S(),this.#C&&(c||this.#_(p,s,r),o&&this.#T(o,p)),!n&&this.#w&&this.#g){const e=this.#g;let t;for(;t=e?.shift();)this.#r?.(...t)}return this}pop(){try{for(;this.#a;){const e=this.#p[this.#d];if(this.#O(!0),this.#I(e)){if(e.__staleWhileFetching)return e.__staleWhileFetching}else if(void 0!==e)return e}}finally{if(this.#w&&this.#g){const e=this.#g;let t;for(;t=e?.shift();)this.#r?.(...t)}}}#O(e){const t=this.#d,i=this.#l[t],s=this.#p[t];return this.#v&&this.#I(s)?s.__abortController.abort(new Error("evicted")):(this.#y||this.#w)&&(this.#y&&this.#s?.(s,i,"evict"),this.#w&&this.#g?.push([s,i,"evict"])),this.#F(t),e&&(this.#l[t]=void 0,this.#p[t]=void 0,this.#m.push(t)),1===this.#a?(this.#d=this.#h=0,this.#m.length=0):this.#d=this.#A[t],this.#c.delete(i),this.#a--,t}has(e,t={}){const{updateAgeOnHas:i=this.updateAgeOnHas,status:s}=t,r=this.#c.get(e);if(void 0!==r){const e=this.#p[r];if(this.#I(e)&&void 0===e.__staleWhileFetching)return!1;if(!this.#k(r))return i&&this.#R(r),s&&(s.has="hit",this.#T(s,r)),!0;s&&(s.has="stale",this.#T(s,r))}else s&&(s.has="miss");return!1}peek(e,t={}){const{allowStale:i=this.allowStale}=t,s=this.#c.get(e);if(void 0!==s&&(i||!this.#k(s))){const e=this.#p[s];return this.#I(e)?e.__staleWhileFetching:e}}#B(e,t,i,s){const r=void 0===t?void 0:this.#p[t];if(this.#I(r))return r;const n=new U,{signal:a}=i;a?.addEventListener("abort",(()=>n.abort(a.reason)),{signal:n.signal});const o={signal:n.signal,options:i,context:s},c=(s,r=!1)=>{const{aborted:a}=n.signal,c=i.ignoreFetchAbort&&void 0!==s;if(i.status&&(a&&!r?(i.status.fetchAborted=!0,i.status.fetchError=n.signal.reason,c&&(i.status.fetchAbortIgnored=!0)):i.status.fetchResolved=!0),a&&!c&&!r)return l(n.signal.reason);const A=p;return this.#p[t]===p&&(void 0===s?A.__staleWhileFetching?this.#p[t]=A.__staleWhileFetching:this.delete(e):(i.status&&(i.status.fetchUpdated=!0),this.set(e,s,o.options))),s},l=s=>{const{aborted:r}=n.signal,a=r&&i.allowStaleOnFetchAbort,o=a||i.allowStaleOnFetchRejection,c=o||i.noDeleteOnFetchRejection,l=p;if(this.#p[t]===p&&(c&&void 0!==l.__staleWhileFetching?a||(this.#p[t]=l.__staleWhileFetching):this.delete(e)),o)return i.status&&void 0!==l.__staleWhileFetching&&(i.status.returnedStale=!0),l.__staleWhileFetching;if(l.__returned===l)throw s};i.status&&(i.status.fetchDispatched=!0);const p=new Promise(((t,s)=>{const a=this.#n?.(e,r,o);a&&a instanceof Promise&&a.then((e=>t(e)),s),n.signal.addEventListener("abort",(()=>{i.ignoreFetchAbort&&!i.allowStaleOnFetchAbort||(t(),i.allowStaleOnFetchAbort&&(t=e=>c(e,!0)))}))})).then(c,(e=>(i.status&&(i.status.fetchRejected=!0,i.status.fetchError=e),l(e)))),A=Object.assign(p,{__abortController:n,__staleWhileFetching:r,__returned:void 0});return void 0===t?(this.set(e,A,{...o.options,status:void 0}),t=this.#c.get(e)):this.#p[t]=A,A}#I(e){if(!this.#v)return!1;const t=e;return!!t&&t instanceof Promise&&t.hasOwnProperty("__staleWhileFetching")&&t.__abortController instanceof U}async fetch(e,t={}){const{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:r=this.noDeleteOnStaleGet,ttl:n=this.ttl,noDisposeOnSet:a=this.noDisposeOnSet,size:o=0,sizeCalculation:c=this.sizeCalculation,noUpdateTTL:l=this.noUpdateTTL,noDeleteOnFetchRejection:p=this.noDeleteOnFetchRejection,allowStaleOnFetchRejection:A=this.allowStaleOnFetchRejection,ignoreFetchAbort:u=this.ignoreFetchAbort,allowStaleOnFetchAbort:d=this.allowStaleOnFetchAbort,context:h,forceRefresh:m=!1,status:g,signal:f}=t;if(!this.#v)return g&&(g.fetch="get"),this.get(e,{allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:r,status:g});const E={allowStale:i,updateAgeOnGet:s,noDeleteOnStaleGet:r,ttl:n,noDisposeOnSet:a,size:o,sizeCalculation:c,noUpdateTTL:l,noDeleteOnFetchRejection:p,allowStaleOnFetchRejection:A,allowStaleOnFetchAbort:d,ignoreFetchAbort:u,status:g,signal:f};let C=this.#c.get(e);if(void 0===C){g&&(g.fetch="miss");const t=this.#B(e,C,E,h);return t.__returned=t}{const t=this.#p[C];if(this.#I(t)){const e=i&&void 0!==t.__staleWhileFetching;return g&&(g.fetch="inflight",e&&(g.returnedStale=!0)),e?t.__staleWhileFetching:t.__returned=t}const r=this.#k(C);if(!m&&!r)return g&&(g.fetch="hit"),this.#b(C),s&&this.#R(C),g&&this.#T(g,C),t;const n=this.#B(e,C,E,h),a=void 0!==n.__staleWhileFetching&&i;return g&&(g.fetch=r?"stale":"refresh",a&&r&&(g.returnedStale=!0)),a?n.__staleWhileFetching:n.__returned=n}}get(e,t={}){const{allowStale:i=this.allowStale,updateAgeOnGet:s=this.updateAgeOnGet,noDeleteOnStaleGet:r=this.noDeleteOnStaleGet,status:n}=t,a=this.#c.get(e);if(void 0!==a){const t=this.#p[a],o=this.#I(t);return n&&this.#T(n,a),this.#k(a)?(n&&(n.get="stale"),o?(n&&i&&void 0!==t.__staleWhileFetching&&(n.returnedStale=!0),i?t.__staleWhileFetching:void 0):(r||this.delete(e),n&&i&&(n.returnedStale=!0),i?t:void 0)):(n&&(n.get="hit"),o?t.__staleWhileFetching:(this.#b(a),s&&this.#R(a),t))}n&&(n.get="miss")}#U(e,t){this.#u[t]=e,this.#A[e]=t}#b(e){e!==this.#h&&(e===this.#d?this.#d=this.#A[e]:this.#U(this.#u[e],this.#A[e]),this.#U(this.#h,e),this.#h=e)}delete(e){let t=!1;if(0!==this.#a){const i=this.#c.get(e);if(void 0!==i)if(t=!0,1===this.#a)this.clear();else{this.#F(i);const t=this.#p[i];this.#I(t)?t.__abortController.abort(new Error("deleted")):(this.#y||this.#w)&&(this.#y&&this.#s?.(t,e,"delete"),this.#w&&this.#g?.push([t,e,"delete"])),this.#c.delete(e),this.#l[i]=void 0,this.#p[i]=void 0,i===this.#h?this.#h=this.#u[i]:i===this.#d?this.#d=this.#A[i]:(this.#A[this.#u[i]]=this.#A[i],this.#u[this.#A[i]]=this.#u[i]),this.#a--,this.#m.push(i)}}if(this.#w&&this.#g?.length){const e=this.#g;let t;for(;t=e?.shift();)this.#r?.(...t)}return t}clear(){for(const e of this.#x({allowStale:!0})){const t=this.#p[e];if(this.#I(t))t.__abortController.abort(new Error("deleted"));else{const i=this.#l[e];this.#y&&this.#s?.(t,i,"delete"),this.#w&&this.#g?.push([t,i,"delete"])}}if(this.#c.clear(),this.#p.fill(void 0),this.#l.fill(void 0),this.#C&&this.#E&&(this.#C.fill(0),this.#E.fill(0)),this.#f&&this.#f.fill(0),this.#d=0,this.#h=0,this.#m.length=0,this.#o=0,this.#a=0,this.#w&&this.#g){const e=this.#g;let t;for(;t=e?.shift();)this.#r?.(...t)}}}var q=i(96882);async function Y({appId:e,privateKey:t,timeDifference:i}){try{const s=await async function({id:e,privateKey:t,now:i=Math.floor(Date.now()/1e3)}){const s=i-30,r=s+600,n={iat:s,exp:r,iss:e};return{appId:e,expiration:r,token:await F({privateKey:t,payload:n})}}({id:+e,privateKey:t,now:i&&Math.floor(Date.now()/1e3)+i});return{type:"app",token:s.token,appId:s.appId,expiresAt:new Date(1e3*s.expiration).toISOString()}}catch(e){throw"-----BEGIN RSA PRIVATE KEY-----"===t?new Error("The 'privateKey` option contains only the first line '-----BEGIN RSA PRIVATE KEY-----'. If you are setting it using a `.env` file, make sure it is set on a single line with newlines replaced by '\n'"):e}}function W({installationId:e,permissions:t={},repositoryIds:i=[],repositoryNames:s=[]}){const r=Object.keys(t).sort().map((e=>"read"===t[e]?e:`${e}!`)).join(",");return[e,i.sort().join(","),s.join(","),r].filter(Boolean).join("|")}function z({installationId:e,token:t,createdAt:i,expiresAt:s,repositorySelection:r,permissions:n,repositoryIds:a,repositoryNames:o,singleFileName:c}){return Object.assign({type:"token",tokenType:"installation",token:t,installationId:e,permissions:n,createdAt:i,expiresAt:s,repositorySelection:r},a?{repositoryIds:a}:null,o?{repositoryNames:o}:null,c?{singleFileName:c}:null)}async function $(e,t,i){const s=Number(t.installationId||e.installationId);if(!s)throw new Error("[@octokit/auth-app] installationId option is required for installation authentication.");if(t.factory){const{type:i,factory:s,oauthApp:r,...n}={...e,...t};return s(n)}const r=Object.assign({installationId:s},t);if(!t.refresh){const t=await async function(e,t){const i=W(t),s=await e.get(i);if(!s)return;const[r,n,a,o,c,l]=s.split("|");return{token:r,createdAt:n,expiresAt:a,permissions:t.permissions||c.split(/,/).reduce(((e,t)=>(/!$/.test(t)?e[t.slice(0,-1)]="write":e[t]="read",e)),{}),repositoryIds:t.repositoryIds,repositoryNames:t.repositoryNames,singleFileName:l,repositorySelection:o}}(e.cache,r);if(t){const{token:e,createdAt:i,expiresAt:r,permissions:n,repositoryIds:a,repositoryNames:o,singleFileName:c,repositorySelection:l}=t;return z({installationId:s,token:e,createdAt:i,expiresAt:r,permissions:n,repositorySelection:l,repositoryIds:a,repositoryNames:o,singleFileName:c})}}const n=await Y(e),a=i||e.request,{data:{token:o,expires_at:c,repositories:l,permissions:p,repository_selection:A,single_file:u}}=await a("POST /app/installations/{installation_id}/access_tokens",{installation_id:s,repository_ids:t.repositoryIds,repositories:t.repositoryNames,permissions:t.permissions,mediaType:{previews:["machine-man"]},headers:{authorization:`bearer ${n.token}`}}),d=p||{},h=A||"all",m=l?l.map((e=>e.id)):void 0,g=l?l.map((e=>e.name)):void 0,f=(new Date).toISOString();return await async function(e,t,i){const s=W(t),r=t.permissions?"":Object.keys(i.permissions).map((e=>`${e}${"write"===i.permissions[e]?"!":""}`)).join(","),n=[i.token,i.createdAt,i.expiresAt,i.repositorySelection,r,i.singleFileName].join("|");await e.set(s,n)}(e.cache,r,{token:o,createdAt:f,expiresAt:c,repositorySelection:h,permissions:d,repositoryIds:m,repositoryNames:g,singleFileName:u}),z({installationId:s,token:o,createdAt:f,expiresAt:c,repositorySelection:h,permissions:d,repositoryIds:m,repositoryNames:g,singleFileName:u})}async function X(e,t){switch(t.type){case"app":return Y(e);case"oauth":e.log.warn(new S.$('[@octokit/auth-app] {type: "oauth"} is deprecated. Use {type: "oauth-app"} instead'));case"oauth-app":return e.oauthApp({type:"oauth-app"});case"installation":return $(e,{...t,type:"installation"});case"oauth-user":return e.oauthApp(t);default:throw new Error(`Invalid auth type: ${t.type}`)}}var K=function(e){const t=`^(?:${["/app","/app/hook/config","/app/hook/deliveries","/app/hook/deliveries/{delivery_id}","/app/hook/deliveries/{delivery_id}/attempts","/app/installations","/app/installations/{installation_id}","/app/installations/{installation_id}/access_tokens","/app/installations/{installation_id}/suspended","/marketplace_listing/accounts/{account_id}","/marketplace_listing/plan","/marketplace_listing/plans","/marketplace_listing/plans/{plan_id}/accounts","/marketplace_listing/stubbed/accounts/{account_id}","/marketplace_listing/stubbed/plan","/marketplace_listing/stubbed/plans","/marketplace_listing/stubbed/plans/{plan_id}/accounts","/orgs/{org}/installation","/repos/{owner}/{repo}/installation","/users/{username}/installation"].map((e=>e.split("/").map((e=>e.startsWith("{")?"(?:.+?)":e)).join("/"))).map((e=>`(?:${e})`)).join("|")})$`;return new RegExp(t,"i")}(),Z=5e3;async function ee(e,t,i,s){const r=t.endpoint.merge(i,s),n=r.url;if(/\/login\/oauth\/access_token$/.test(n))return t(r);if(function(e){return!!e&&K.test(e.split("?")[0])}(n.replace(t.endpoint.DEFAULTS.baseUrl,""))){const{token:i}=await Y(e);let s;r.headers.authorization=`bearer ${i}`;try{s=await t(r)}catch(i){if(function(e){return!(e.message.match(/'Expiration time' claim \('exp'\) must be a numeric value representing the future time at which the assertion expires/)||e.message.match(/'Issued at' claim \('iat'\) must be an Integer representing the time that the assertion was issued/))}(i))throw i;if(void 0===i.response.headers.date)throw i;const s=Math.floor((Date.parse(i.response.headers.date)-Date.parse((new Date).toString()))/1e3);e.log.warn(i.message),e.log.warn(`[@octokit/auth-app] GitHub API time and system time are different by ${s} seconds. Retrying request with the difference accounted for.`);const{token:n}=await Y({...e,timeDifference:s});return r.headers.authorization=`bearer ${n}`,t(r)}return s}if((0,q.X)(n)){const i=await e.oauthApp({type:"oauth-app"});return r.headers.authorization=i.headers.authorization,t(r)}const{token:a,createdAt:o}=await $(e,{},t);return r.headers.authorization=`token ${a}`,te(e,t,r,o)}async function te(e,t,i,s,r=0){const n=+new Date-+new Date(s);try{return await t(i)}catch(a){if(401!==a.status)throw a;if(n>=Z)throw r>0&&(a.message=`After ${r} retries within ${n/1e3}s of creating the installation access token, the response remains 401. At this point, the cause may be an authentication problem or a system outage. Please check https://www.githubstatus.com for status information`),a;const o=1e3*++r;return e.log.warn(`[@octokit/auth-app] Retrying after 401 response to account for token replication delay (retry: ${r}, wait: ${o/1e3}s)`),await new Promise((e=>setTimeout(e,o))),te(e,t,i,s,r)}}var ie="4.0.13";function se(e){if(!e.appId)throw new Error("[@octokit/auth-app] appId option is required");if(!Number.isFinite(+e.appId))throw new Error("[@octokit/auth-app] appId option must be a number or numeric string");if(!e.privateKey)throw new Error("[@octokit/auth-app] privateKey option is required");if("installationId"in e&&!e.installationId)throw new Error("[@octokit/auth-app] installationId is set to a falsy value");const t=Object.assign({warn:console.warn.bind(console)},e.log),i=e.request||k.W.defaults({headers:{"user-agent":`octokit-auth-app.js/${ie} ${(0,x.getUserAgent)()}`}}),s=Object.assign({request:i,cache:new H({max:15e3,ttl:354e4})},e,e.installationId?{installationId:Number(e.installationId)}:{},{log:t,oauthApp:(0,D.createOAuthAppAuth)({clientType:"github-app",clientId:e.clientId||"",clientSecret:e.clientSecret||"",request:i})});return Object.assign(X.bind(null,s),{hook:ee.bind(null,s)})}var re=i(44929),ne=i(22282),ae=i(14686),oe=i.n(ae),ce=i(6113),le=(e=>(e.SHA1="sha1",e.SHA256="sha256",e))(le||{});const pe="3.0.3";async function Ae(e,t){const{secret:i,algorithm:s}="object"==typeof e?{secret:e.secret,algorithm:e.algorithm||le.SHA256}:{secret:e,algorithm:le.SHA256};if(!i||!t)throw new TypeError("[@octokit/webhooks-methods] secret & payload required for sign()");if(!Object.values(le).includes(s))throw new TypeError(`[@octokit/webhooks] Algorithm ${s} is not supported. Must be 'sha1' or 'sha256'`);return`${s}=${(0,ce.createHmac)(s,i).update(t).digest("hex")}`}Ae.VERSION=pe;var ue=i(14300);async function de(e,t,i){if(!e||!t||!i)throw new TypeError("[@octokit/webhooks-methods] secret, eventPayload & signature required");const s=ue.Buffer.from(i),r=(e=>e.startsWith("sha256=")?"sha256":"sha1")(i),n=ue.Buffer.from(await Ae({secret:e,algorithm:r},t));return s.length===n.length&&(0,ce.timingSafeEqual)(s,n)}de.VERSION=pe;var he=e=>({debug:()=>{},info:()=>{},warn:console.warn.bind(console),error:console.error.bind(console),...e}),me=["branch_protection_rule","branch_protection_rule.created","branch_protection_rule.deleted","branch_protection_rule.edited","check_run","check_run.completed","check_run.created","check_run.requested_action","check_run.rerequested","check_suite","check_suite.completed","check_suite.requested","check_suite.rerequested","code_scanning_alert","code_scanning_alert.appeared_in_branch","code_scanning_alert.closed_by_user","code_scanning_alert.created","code_scanning_alert.fixed","code_scanning_alert.reopened","code_scanning_alert.reopened_by_user","commit_comment","commit_comment.created","create","delete","dependabot_alert","dependabot_alert.created","dependabot_alert.dismissed","dependabot_alert.fixed","dependabot_alert.reintroduced","dependabot_alert.reopened","deploy_key","deploy_key.created","deploy_key.deleted","deployment","deployment.created","deployment_status","deployment_status.created","discussion","discussion.answered","discussion.category_changed","discussion.created","discussion.deleted","discussion.edited","discussion.labeled","discussion.locked","discussion.pinned","discussion.transferred","discussion.unanswered","discussion.unlabeled","discussion.unlocked","discussion.unpinned","discussion_comment","discussion_comment.created","discussion_comment.deleted","discussion_comment.edited","fork","github_app_authorization","github_app_authorization.revoked","gollum","installation","installation.created","installation.deleted","installation.new_permissions_accepted","installation.suspend","installation.unsuspend","installation_repositories","installation_repositories.added","installation_repositories.removed","installation_target","installation_target.renamed","issue_comment","issue_comment.created","issue_comment.deleted","issue_comment.edited","issues","issues.assigned","issues.closed","issues.deleted","issues.demilestoned","issues.edited","issues.labeled","issues.locked","issues.milestoned","issues.opened","issues.pinned","issues.reopened","issues.transferred","issues.unassigned","issues.unlabeled","issues.unlocked","issues.unpinned","label","label.created","label.deleted","label.edited","marketplace_purchase","marketplace_purchase.cancelled","marketplace_purchase.changed","marketplace_purchase.pending_change","marketplace_purchase.pending_change_cancelled","marketplace_purchase.purchased","member","member.added","member.edited","member.removed","membership","membership.added","membership.removed","merge_group","merge_group.checks_requested","meta","meta.deleted","milestone","milestone.closed","milestone.created","milestone.deleted","milestone.edited","milestone.opened","org_block","org_block.blocked","org_block.unblocked","organization","organization.deleted","organization.member_added","organization.member_invited","organization.member_removed","organization.renamed","package","package.published","package.updated","page_build","ping","project","project.closed","project.created","project.deleted","project.edited","project.reopened","project_card","project_card.converted","project_card.created","project_card.deleted","project_card.edited","project_card.moved","project_column","project_column.created","project_column.deleted","project_column.edited","project_column.moved","projects_v2_item","projects_v2_item.archived","projects_v2_item.converted","projects_v2_item.created","projects_v2_item.deleted","projects_v2_item.edited","projects_v2_item.reordered","projects_v2_item.restored","public","pull_request","pull_request.assigned","pull_request.auto_merge_disabled","pull_request.auto_merge_enabled","pull_request.closed","pull_request.converted_to_draft","pull_request.demilestoned","pull_request.dequeued","pull_request.edited","pull_request.labeled","pull_request.locked","pull_request.milestoned","pull_request.opened","pull_request.queued","pull_request.ready_for_review","pull_request.reopened","pull_request.review_request_removed","pull_request.review_requested","pull_request.synchronize","pull_request.unassigned","pull_request.unlabeled","pull_request.unlocked","pull_request_review","pull_request_review.dismissed","pull_request_review.edited","pull_request_review.submitted","pull_request_review_comment","pull_request_review_comment.created","pull_request_review_comment.deleted","pull_request_review_comment.edited","pull_request_review_thread","pull_request_review_thread.resolved","pull_request_review_thread.unresolved","push","registry_package","registry_package.published","registry_package.updated","release","release.created","release.deleted","release.edited","release.prereleased","release.published","release.released","release.unpublished","repository","repository.archived","repository.created","repository.deleted","repository.edited","repository.privatized","repository.publicized","repository.renamed","repository.transferred","repository.unarchived","repository_dispatch","repository_import","repository_vulnerability_alert","repository_vulnerability_alert.create","repository_vulnerability_alert.dismiss","repository_vulnerability_alert.reopen","repository_vulnerability_alert.resolve","secret_scanning_alert","secret_scanning_alert.created","secret_scanning_alert.reopened","secret_scanning_alert.resolved","security_advisory","security_advisory.performed","security_advisory.published","security_advisory.updated","security_advisory.withdrawn","sponsorship","sponsorship.cancelled","sponsorship.created","sponsorship.edited","sponsorship.pending_cancellation","sponsorship.pending_tier_change","sponsorship.tier_changed","star","star.created","star.deleted","status","team","team.added_to_repository","team.created","team.deleted","team.edited","team.removed_from_repository","team_add","watch","watch.started","workflow_dispatch","workflow_job","workflow_job.completed","workflow_job.in_progress","workflow_job.queued","workflow_run","workflow_run.completed","workflow_run.in_progress","workflow_run.requested"];function ge(e,t,i){e.hooks[t]||(e.hooks[t]=[]),e.hooks[t].push(i)}function fe(e,t,i){if(Array.isArray(t))t.forEach((t=>fe(e,t,i)));else{if(["*","error"].includes(t)){const e="*"===t?"any":t,i=`Using the "${t}" event with the regular Webhooks.on() function is not supported. Please use the Webhooks.on${e.charAt(0).toUpperCase()+e.slice(1)}() method instead`;throw new Error(i)}me.includes(t)||e.log.warn(`"${t}" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)`),ge(e,t,i)}}function Ee(e,t){ge(e,"*",t)}function Ce(e,t){ge(e,"error",t)}function ye(e,t){let i;try{i=e(t)}catch(e){console.log('FATAL: Error occurred in "error" event handler'),console.log(e)}i&&i.catch&&i.catch((e=>{console.log('FATAL: Error occurred in "error" event handler'),console.log(e)}))}function ve(e,t){const i=e.hooks.error||[];if(t instanceof Error){const e=Object.assign(new(oe())([t]),{event:t,errors:[t]});return i.forEach((t=>ye(t,e))),Promise.reject(e)}if(!t||!t.name)throw new(oe())(["Event name not passed"]);if(!t.payload)throw new(oe())(["Event payload not passed"]);const s=function(e,t,i){const s=[e.hooks[i],e.hooks["*"]];return t&&s.unshift(e.hooks[`${i}.${t}`]),[].concat(...s.filter(Boolean))}(e,"action"in t.payload?t.payload.action:null,t.name);if(0===s.length)return Promise.resolve();const r=[],n=s.map((i=>{let s=Promise.resolve(t);return e.transform&&(s=s.then(e.transform)),s.then((e=>i(e))).catch((e=>r.push(Object.assign(e,{event:t}))))}));return Promise.all(n).then((()=>{if(0===r.length)return;const e=new(oe())(r);throw Object.assign(e,{event:t,errors:r}),i.forEach((t=>ye(t,e))),e}))}function we(e,t,i){if(Array.isArray(t))t.forEach((t=>we(e,t,i)));else if(e.hooks[t])for(let s=e.hooks[t].length-1;s>=0;s--)if(e.hooks[t][s]===i)return void e.hooks[t].splice(s,1)}function Ie(e){const t={hooks:{},log:he(e&&e.log)};return e&&e.transform&&(t.transform=e.transform),{on:fe.bind(null,t),onAny:Ee.bind(null,t),onError:Ce.bind(null,t),removeListener:we.bind(null,t),receive:ve.bind(null,t)}}function Be(e){return JSON.stringify(e).replace(/[^\\]\\u[\da-f]{4}/g,(e=>e.substr(0,3)+e.substr(3).toUpperCase()))}async function be(e,t){return Ae(e,"string"==typeof t?t:Be(t))}var Qe=["x-github-event","x-hub-signature-256","x-github-delivery"];async function xe(e,t,i,s,r){let n;try{n=new URL(i.url,"http://localhost").pathname}catch(e){return s.writeHead(422,{"content-type":"application/json"}),void s.end(JSON.stringify({error:`Request URL could not be parsed: ${i.url}`}))}if("POST"!==i.method||n!==t.path)return"function"==typeof r?r():t.onUnhandledRequest(i,s);if(!i.headers["content-type"]||!i.headers["content-type"].startsWith("application/json"))return s.writeHead(415,{"content-type":"application/json",accept:"application/json"}),void s.end(JSON.stringify({error:'Unsupported "Content-Type" header value. Must be "application/json"'}));const a=function(e){return Qe.filter((t=>!(t in e.headers)))}(i).join(", ");if(a)return s.writeHead(400,{"content-type":"application/json"}),void s.end(JSON.stringify({error:`Required headers missing: ${a}`}));const o=i.headers["x-github-event"],c=i.headers["x-hub-signature-256"],l=i.headers["x-github-delivery"];t.log.debug(`${o} event received (id: ${l})`);let p=!1;const A=setTimeout((()=>{p=!0,s.statusCode=202,s.end("still processing\n")}),9e3).unref();try{const t=await function(e){return e.body?("string"!=typeof e.body&&console.warn("[@octokit/webhooks] Passing the payload as a JSON object in `request.body` is deprecated and will be removed in a future release of `@octokit/webhooks`, please pass it as a a `string` instead."),Promise.resolve(e.body)):new Promise(((t,i)=>{let s="";e.setEncoding("utf8"),e.on("error",(e=>i(new(oe())([e])))),e.on("data",(e=>s+=e)),e.on("end",(()=>{try{JSON.parse(s),t(s)}catch(e){e.message="Invalid JSON",e.status=400,i(new(oe())([e]))}}))}))}(i);if(await e.verifyAndReceive({id:l,name:o,payload:t,signature:c}),clearTimeout(A),p)return;s.end("ok\n")}catch(e){if(clearTimeout(A),p)return;const i=Array.from(e)[0],r=i.message?`${i.name}: ${i.message}`:"Error: An Unspecified error occurred";s.statusCode=void 0!==i.status?i.status:500,t.log.error(e),s.end(JSON.stringify({error:r}))}}function ke(e,t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:`Unknown route: ${e.method} ${e.url}`}))}var De=class{constructor(e){if(!e||!e.secret)throw new Error("[@octokit/webhooks] options.secret required");const t={eventHandler:Ie(e),secret:e.secret,hooks:{},log:he(e.log)};this.sign=be.bind(null,e.secret),this.verify=(t,i)=>("object"==typeof t&&console.warn("[@octokit/webhooks] Passing a JSON payload object to `verify()` is deprecated and the functionality will be removed in a future release of `@octokit/webhooks`"),async function(e,t,i){return de(e,"string"==typeof t?t:Be(t),i)}(e.secret,t,i)),this.on=t.eventHandler.on,this.onAny=t.eventHandler.onAny,this.onError=t.eventHandler.onError,this.removeListener=t.eventHandler.removeListener,this.receive=t.eventHandler.receive,this.verifyAndReceive=e=>("object"==typeof e.payload&&console.warn("[@octokit/webhooks] Passing a JSON payload object to `verifyAndReceive()` is deprecated and the functionality will be removed in a future release of `@octokit/webhooks`"),async function(e,t){if(!await de(e.secret,"object"==typeof t.payload?Be(t.payload):t.payload,t.signature).catch((()=>!1))){const i=new Error("[@octokit/webhooks] signature does not match event payload and secret");return e.eventHandler.receive(Object.assign(i,{event:t,status:400}))}return e.eventHandler.receive({id:t.id,name:t.name,payload:"string"==typeof t.payload?JSON.parse(t.payload):t.payload})}(t,e))}};async function Se(e,t){return e.octokit.auth({type:"installation",installationId:t,factory(e){const i={...e.octokitOptions,authStrategy:se,auth:{...e,installationId:t}};return new e.octokit.constructor(i)}})}function _e(e){return Object.assign(Re.bind(null,e),{iterator:Te.bind(null,e)})}async function Re(e,t){const i=Te(e)[Symbol.asyncIterator]();let s=await i.next();for(;!s.done;)await t(s.value),s=await i.next()}function Te(e){return{async*[Symbol.asyncIterator](){const t=o.iterator(e.octokit,"GET /app/installations");for await(const{data:i}of t)for(const t of i){const i=await Se(e,t.id);yield{octokit:i,installation:t}}}}}function Fe(e){return Object.assign(Ne.bind(null,e),{iterator:Le.bind(null,e)})}async function Ne(e,t,i){const s=Le(e,i?t:void 0)[Symbol.asyncIterator]();let r=await s.next();for(;!r.done;)i?await i(r.value):await t(r.value),r=await s.next()}function Le(e,t){return{async*[Symbol.asyncIterator](){const i=t?function(e,t){return{async*[Symbol.asyncIterator](){yield{octokit:await e.getInstallationOctokit(t)}}}}(e,t.installationId):e.eachInstallation.iterator();for await(const{octokit:e}of i){const t=o.iterator(e,"GET /installation/repositories");for await(const{data:i}of t)for(const t of i)yield{octokit:e,repository:t}}}}}function Oe(e,t){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:`Unknown route: ${e.method} ${e.url}`}))}function Me(){}function Ue(e,t={}){const i=Object.assign({debug:Me,info:Me,warn:console.warn.bind(console),error:console.error.bind(console)},t.log),s={onUnhandledRequest:Oe,pathPrefix:"/api/github",...t,log:i},r=function(e,{path:t="/api/github/webhooks",onUnhandledRequest:i=ke,log:s=he()}={}){return xe.bind(null,e,{path:t,onUnhandledRequest:(e,t)=>(console.warn("[@octokit/webhooks] `onUnhandledRequest()` is deprecated and will be removed in a future release of `@octokit/webhooks`"),i(e,t)),log:s})}(e.webhooks,{path:s.pathPrefix+"/webhooks",log:i,onUnhandledRequest:s.onUnhandledRequest}),n=(0,re.createNodeMiddleware)(e.oauth,{pathPrefix:s.pathPrefix+"/oauth",onUnhandledRequest:s.onUnhandledRequest});return Pe.bind(null,s,{webhooksMiddleware:r,oauthMiddleware:n})}async function Pe(e,{webhooksMiddleware:t,oauthMiddleware:i},s,r,n){const{pathname:a}=new URL(s.url,"http://localhost");return a===`${e.pathPrefix}/webhooks`?t(s,r,n):a.startsWith(`${e.pathPrefix}/oauth/`)?i(s,r,n):"function"==typeof n?n():e.onUnhandledRequest(s,r)}var Ge=class{static defaults(e){return class extends(this){constructor(...t){super({...e,...t[0]})}}}constructor(e){const t=e.Octokit||s.Octokit,i=Object.assign({appId:e.appId,privateKey:e.privateKey},e.oauth?{clientId:e.oauth.clientId,clientSecret:e.oauth.clientSecret}:{});this.octokit=new t({authStrategy:se,auth:i,log:e.log}),this.log=Object.assign({debug:()=>{},info:()=>{},warn:console.warn.bind(console),error:console.error.bind(console)},e.log),e.webhooks?this.webhooks=function(e,t){return new De({secret:t.secret,transform:async t=>{if(!("installation"in t.payload)||"object"!=typeof t.payload.installation){const i=new e.constructor({authStrategy:ne.createUnauthenticatedAuth,auth:{reason:'"installation" key missing in webhook event payload'}});return{...t,octokit:i}}const i=t.payload.installation.id,s=await e.auth({type:"installation",installationId:i,factory:e=>new e.octokit.constructor({...e.octokitOptions,authStrategy:se,auth:{...e,installationId:i}})});return s.hook.before("request",(e=>{e.headers["x-github-delivery"]=t.id})),{...t,octokit:s}}})}(this.octokit,e.webhooks):Object.defineProperty(this,"webhooks",{get(){throw new Error("[@octokit/app] webhooks option not set")}}),e.oauth?this.oauth=new re.OAuthApp({...e.oauth,clientType:"github-app",Octokit:t}):Object.defineProperty(this,"oauth",{get(){throw new Error("[@octokit/app] oauth.clientId / oauth.clientSecret options are not set")}}),this.getInstallationOctokit=Se.bind(null,this),this.eachInstallation=_e(this),this.eachRepository=Fe(this)}};Ge.VERSION="13.1.8";var je=s.Octokit.plugin(u,c,C,Q).defaults({userAgent:"octokit.js/2.1.0",throttle:{onRateLimit:function(e,t,i){if(i.log.warn(`Request quota exhausted for request ${t.method} ${t.url}`),0===t.request.retryCount)return i.log.info(`Retrying after ${e} seconds!`),!0},onSecondaryRateLimit:function(e,t,i){if(i.log.warn(`SecondaryRateLimit detected for request ${t.method} ${t.url}`),0===t.request.retryCount)return i.log.info(`Retrying after ${e} seconds!`),!0}}}),Ve=Ge.defaults({Octokit:je}),Je=re.OAuthApp.defaults({Octokit:je})},36219:(e,t,i)=>{var s=i(42065);function r(e){var t=function(){return t.called?t.value:(t.called=!0,t.value=e.apply(this,arguments))};return t.called=!1,t}function n(e){var t=function(){if(t.called)throw new Error(t.onceError);return t.called=!0,t.value=e.apply(this,arguments)},i=e.name||"Function wrapped with `once`";return t.onceError=i+" shouldn't be called more than once",t.called=!1,t}e.exports=s(r),e.exports.strict=s(n),r.proto=r((function(){Object.defineProperty(Function.prototype,"once",{value:function(){return r(this)},configurable:!0}),Object.defineProperty(Function.prototype,"onceStrict",{value:function(){return n(this)},configurable:!0})}))},86015:(e,t,i)=>{"use strict";const s=i(70474),r=new WeakMap,n=(e,t={})=>{if("function"!=typeof e)throw new TypeError("Expected a function");let i,n=0;const a=e.displayName||e.name||"",o=function(...s){if(r.set(o,++n),1===n)i=e.apply(this,s),e=null;else if(!0===t.throw)throw new Error(`Function \`${a}\` can only be called once`);return i};return s(o,e),r.set(o,n),o};e.exports=n,e.exports.default=n,e.exports.callCount=e=>{if(!r.has(e))throw new Error(`The given function \`${e.name}\` is not wrapped by the \`onetime\` package`);return r.get(e)}},7540:(e,t,i)=>{"use strict";const s=i(14521),r=i(47309),n=i(55693),a=i(60881),o=i(6853),c=i(20281),l=i(80762),p=i(27759),A=i(5881),{BufferListStream:u}=i(26221),d=Symbol("text"),h=Symbol("prefixText");class m{constructor(){this.requests=0,this.mutedStream=new u,this.mutedStream.pipe(process.stdout);const e=this;this.ourEmit=function(t,i,...s){const{stdin:r}=process;if(e.requests>0||r.emit===e.ourEmit){if("keypress"===t)return;"data"===t&&i.includes(3)&&process.emit("SIGINT"),Reflect.apply(e.oldEmit,this,[t,i,...s])}else Reflect.apply(process.stdin.emit,this,[t,i,...s])}}start(){this.requests++,1===this.requests&&this.realStart()}stop(){if(this.requests<=0)throw new Error("`stop` called more times than `start`");this.requests--,0===this.requests&&this.realStop()}realStart(){"win32"!==process.platform&&(this.rl=s.createInterface({input:process.stdin,output:this.mutedStream}),this.rl.on("SIGINT",(()=>{0===process.listenerCount("SIGINT")?process.emit("SIGINT"):(this.rl.close(),process.kill(process.pid,"SIGINT"))})))}realStop(){"win32"!==process.platform&&(this.rl.close(),this.rl=void 0)}}let g;class f{constructor(e){g||(g=new m),"string"==typeof e&&(e={text:e}),this.options={text:"",color:"cyan",stream:process.stderr,discardStdin:!0,...e},this.spinner=this.options.spinner,this.color=this.options.color,this.hideCursor=!1!==this.options.hideCursor,this.interval=this.options.interval||this.spinner.interval||100,this.stream=this.options.stream,this.id=void 0,this.isEnabled="boolean"==typeof this.options.isEnabled?this.options.isEnabled:p({stream:this.stream}),this.isSilent="boolean"==typeof this.options.isSilent&&this.options.isSilent,this.text=this.options.text,this.prefixText=this.options.prefixText,this.linesToClear=0,this.indent=this.options.indent,this.discardStdin=this.options.discardStdin,this.isDiscardingStdin=!1}get indent(){return this._indent}set indent(e=0){if(!(e>=0&&Number.isInteger(e)))throw new Error("The `indent` option must be an integer from 0 and up");this._indent=e}_updateInterval(e){void 0!==e&&(this.interval=e)}get spinner(){return this._spinner}set spinner(e){if(this.frameIndex=0,"object"==typeof e){if(void 0===e.frames)throw new Error("The given spinner must have a `frames` property");this._spinner=e}else if(A())if(void 0===e)this._spinner=a.dots;else{if("default"===e||!a[e])throw new Error(`There is no built-in spinner named '${e}'. See https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json for a full list.`);this._spinner=a[e]}else this._spinner=a.line;this._updateInterval(this._spinner.interval)}get text(){return this[d]}set text(e){this[d]=e,this.updateLineCount()}get prefixText(){return this[h]}set prefixText(e){this[h]=e,this.updateLineCount()}get isSpinning(){return void 0!==this.id}getFullPrefixText(e=this[h],t=" "){return"string"==typeof e?e+t:"function"==typeof e?e()+t:""}updateLineCount(){const e=this.stream.columns||80,t=this.getFullPrefixText(this.prefixText,"-");this.lineCount=0;for(const i of c(t+"--"+this[d]).split("\n"))this.lineCount+=Math.max(1,Math.ceil(l(i)/e))}get isEnabled(){return this._isEnabled&&!this.isSilent}set isEnabled(e){if("boolean"!=typeof e)throw new TypeError("The `isEnabled` option must be a boolean");this._isEnabled=e}get isSilent(){return this._isSilent}set isSilent(e){if("boolean"!=typeof e)throw new TypeError("The `isSilent` option must be a boolean");this._isSilent=e}frame(){const{frames:e}=this.spinner;let t=e[this.frameIndex];return this.color&&(t=r[this.color](t)),this.frameIndex=++this.frameIndex%e.length,("string"==typeof this.prefixText&&""!==this.prefixText?this.prefixText+" ":"")+t+("string"==typeof this.text?" "+this.text:"")}clear(){if(!this.isEnabled||!this.stream.isTTY)return this;for(let e=0;e0&&this.stream.moveCursor(0,-1),this.stream.clearLine(),this.stream.cursorTo(this.indent);return this.linesToClear=0,this}render(){return this.isSilent||(this.clear(),this.stream.write(this.frame()),this.linesToClear=this.lineCount),this}start(e){return e&&(this.text=e),this.isSilent?this:this.isEnabled?(this.isSpinning||(this.hideCursor&&n.hide(this.stream),this.discardStdin&&process.stdin.isTTY&&(this.isDiscardingStdin=!0,g.start()),this.render(),this.id=setInterval(this.render.bind(this),this.interval)),this):(this.text&&this.stream.write(`- ${this.text}\n`),this)}stop(){return this.isEnabled?(clearInterval(this.id),this.id=void 0,this.frameIndex=0,this.clear(),this.hideCursor&&n.show(this.stream),this.discardStdin&&process.stdin.isTTY&&this.isDiscardingStdin&&(g.stop(),this.isDiscardingStdin=!1),this):this}succeed(e){return this.stopAndPersist({symbol:o.success,text:e})}fail(e){return this.stopAndPersist({symbol:o.error,text:e})}warn(e){return this.stopAndPersist({symbol:o.warning,text:e})}info(e){return this.stopAndPersist({symbol:o.info,text:e})}stopAndPersist(e={}){if(this.isSilent)return this;const t=e.prefixText||this.prefixText,i=e.text||this.text,s="string"==typeof i?" "+i:"";return this.stop(),this.stream.write(`${this.getFullPrefixText(t," ")}${e.symbol||" "}${s}\n`),this}}e.exports=function(e){return new f(e)},e.exports.promise=(e,t)=>{if("function"!=typeof e.then)throw new TypeError("Parameter `action` must be a Promise");const i=new f(t);return i.start(),(async()=>{try{await e,i.succeed()}catch{i.fail()}})(),i}},23209:e=>{"use strict";e.exports=(e,t)=>(t=t||(()=>{}),e.then((e=>new Promise((e=>{e(t())})).then((()=>e))),(e=>new Promise((e=>{e(t())})).then((()=>{throw e})))))},88464:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const s=i(21883),r=i(26123),n=i(83991),a=()=>{},o=new r.TimeoutError;t.default=class extends s{constructor(e){var t,i,s,r;if(super(),this._intervalCount=0,this._intervalEnd=0,this._pendingCount=0,this._resolveEmpty=a,this._resolveIdle=a,!("number"==typeof(e=Object.assign({carryoverConcurrencyCount:!1,intervalCap:1/0,interval:0,concurrency:1/0,autoStart:!0,queueClass:n.default},e)).intervalCap&&e.intervalCap>=1))throw new TypeError(`Expected \`intervalCap\` to be a number from 1 and up, got \`${null!==(i=null===(t=e.intervalCap)||void 0===t?void 0:t.toString())&&void 0!==i?i:""}\` (${typeof e.intervalCap})`);if(void 0===e.interval||!(Number.isFinite(e.interval)&&e.interval>=0))throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${null!==(r=null===(s=e.interval)||void 0===s?void 0:s.toString())&&void 0!==r?r:""}\` (${typeof e.interval})`);this._carryoverConcurrencyCount=e.carryoverConcurrencyCount,this._isIntervalIgnored=e.intervalCap===1/0||0===e.interval,this._intervalCap=e.intervalCap,this._interval=e.interval,this._queue=new e.queueClass,this._queueClass=e.queueClass,this.concurrency=e.concurrency,this._timeout=e.timeout,this._throwOnTimeout=!0===e.throwOnTimeout,this._isPaused=!1===e.autoStart}get _doesIntervalAllowAnother(){return this._isIntervalIgnored||this._intervalCount{this._onResumeInterval()}),t)),!0;this._intervalCount=this._carryoverConcurrencyCount?this._pendingCount:0}return!1}_tryToStartAnother(){if(0===this._queue.size)return this._intervalId&&clearInterval(this._intervalId),this._intervalId=void 0,this._resolvePromises(),!1;if(!this._isPaused){const e=!this._isIntervalPaused();if(this._doesIntervalAllowAnother&&this._doesConcurrentAllowAnother){const t=this._queue.dequeue();return!!t&&(this.emit("active"),t(),e&&this._initializeIntervalIfNeeded(),!0)}}return!1}_initializeIntervalIfNeeded(){this._isIntervalIgnored||void 0!==this._intervalId||(this._intervalId=setInterval((()=>{this._onInterval()}),this._interval),this._intervalEnd=Date.now()+this._interval)}_onInterval(){0===this._intervalCount&&0===this._pendingCount&&this._intervalId&&(clearInterval(this._intervalId),this._intervalId=void 0),this._intervalCount=this._carryoverConcurrencyCount?this._pendingCount:0,this._processQueue()}_processQueue(){for(;this._tryToStartAnother(););}get concurrency(){return this._concurrency}set concurrency(e){if(!("number"==typeof e&&e>=1))throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${e}\` (${typeof e})`);this._concurrency=e,this._processQueue()}async add(e,t={}){return new Promise(((i,s)=>{this._queue.enqueue((async()=>{this._pendingCount++,this._intervalCount++;try{const n=void 0===this._timeout&&void 0===t.timeout?e():r.default(Promise.resolve(e()),void 0===t.timeout?this._timeout:t.timeout,(()=>{(void 0===t.throwOnTimeout?this._throwOnTimeout:t.throwOnTimeout)&&s(o)}));i(await n)}catch(e){s(e)}this._next()}),t),this._tryToStartAnother(),this.emit("add")}))}async addAll(e,t){return Promise.all(e.map((async e=>this.add(e,t))))}start(){return this._isPaused?(this._isPaused=!1,this._processQueue(),this):this}pause(){this._isPaused=!0}clear(){this._queue=new this._queueClass}async onEmpty(){if(0!==this._queue.size)return new Promise((e=>{const t=this._resolveEmpty;this._resolveEmpty=()=>{t(),e()}}))}async onIdle(){if(0!==this._pendingCount||0!==this._queue.size)return new Promise((e=>{const t=this._resolveIdle;this._resolveIdle=()=>{t(),e()}}))}get size(){return this._queue.size}sizeBy(e){return this._queue.filter(e).length}get pending(){return this._pendingCount}get isPaused(){return this._isPaused}get timeout(){return this._timeout}set timeout(e){this._timeout=e}}},10392:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t,i){let s=0,r=e.length;for(;r>0;){const n=r/2|0;let a=s+n;i(e[a],t)<=0?(s=++a,r-=n+1):r=n}return s}},83991:(e,t,i)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const s=i(10392);t.default=class{constructor(){this._queue=[]}enqueue(e,t){const i={priority:(t=Object.assign({priority:0},t)).priority,run:e};if(this.size&&this._queue[this.size-1].priority>=t.priority)return void this._queue.push(i);const r=s.default(this._queue,i,((e,t)=>t.priority-e.priority));this._queue.splice(r,0,i)}dequeue(){const e=this._queue.shift();return null==e?void 0:e.run}filter(e){return this._queue.filter((t=>t.priority===e.priority)).map((e=>e.run))}get size(){return this._queue.length}}},32656:(e,t,i)=>{"use strict";const s=i(83490),r=["Failed to fetch","NetworkError when attempting to fetch resource.","The Internet connection appears to be offline.","Network request failed"];class n extends Error{constructor(e){super(),e instanceof Error?(this.originalError=e,({message:e}=e)):(this.originalError=new Error(e),this.originalError.stack=this.stack),this.name="AbortError",this.message=e}}const a=(e,t)=>new Promise(((i,a)=>{t={onFailedAttempt:()=>{},retries:10,...t};const o=s.operation(t);o.attempt((async s=>{try{i(await e(s))}catch(e){if(!(e instanceof Error))return void a(new TypeError(`Non-error was thrown: "${e}". You should only throw errors.`));if(e instanceof n)o.stop(),a(e.originalError);else if(e instanceof TypeError&&(c=e.message,!r.includes(c)))o.stop(),a(e);else{((e,t,i)=>{const s=i.retries-(t-1);e.attemptNumber=t,e.retriesLeft=s})(e,s,t);try{await t.onFailedAttempt(e)}catch(e){return void a(e)}o.retry(e)||a(o.mainError())}}var c}))}));e.exports=a,e.exports.default=a,e.exports.AbortError=n},26123:(e,t,i)=>{"use strict";const s=i(23209);class r extends Error{constructor(e){super(e),this.name="TimeoutError"}}const n=(e,t,i)=>new Promise(((n,a)=>{if("number"!=typeof t||t<0)throw new TypeError("Expected `milliseconds` to be a positive number");if(t===1/0)return void n(e);const o=setTimeout((()=>{if("function"==typeof i){try{n(i())}catch(e){a(e)}return}const s=i instanceof Error?i:new r("string"==typeof i?i:`Promise timed out after ${t} milliseconds`);"function"==typeof e.cancel&&e.cancel(),a(s)}),t);s(e.then(n,a),(()=>{clearTimeout(o)}))}));e.exports=n,e.exports.default=n,e.exports.TimeoutError=r},30486:(e,t,i)=>{"use strict";const s=i(30746),r=i(94057),n=e.exports;n.prompt=(e,t)=>(t=r(t),s(e,t)),n.password=(e,t)=>(t=r({silent:!0,trim:!1,default:"",...t}),s(e,t)),n.confirm=(e,t)=>((t=r({trim:!1,...t})).validator.unshift((e=>{switch(e=e.toLowerCase()){case"y":case"yes":case"1":return!0;case"n":case"no":case"0":return!1;default:throw new Error(`Invalid choice: ${e}`)}})),s(e,t)),n.choose=(e,t,i)=>((i=r({trim:!1,...i})).validator.unshift((e=>{const i=t.findIndex((t=>e==t));if(-1===i)throw new Error(`Invalid choice: ${e}`);return t[i]})),s(e,i))},94057:e=>{"use strict";e.exports=function(e){if(void 0!==(e={validator:void 0,retry:!0,trim:!0,default:void 0,useDefaultOnTimeout:!1,silent:!1,replace:"",input:process.stdin,output:process.stdout,timeout:0,...e}).default&&"string"!=typeof e.default)throw new Error("The default option value must be a string");return Array.isArray(e.validator)||(e.validator=e.validator?[e.validator]:[]),e}},30746:(e,t,i)=>{"use strict";const{EOL:s}=i(22037),{promisify:r}=i(73837),n=r(i(91460));e.exports=async function e(t,i){let r;try{r=await n({prompt:t,silent:i.silent,replace:i.replace,input:i.input,output:i.output,timeout:i.timeout})}catch(e){if("timed out"!==e.message||void 0===i.default||!i.useDefaultOnTimeout)throw Object.assign(new Error(e.message),{code:"TIMEDOUT"});r=i.default}if(i.trim&&(r=r.trim()),!r){if(void 0===i.default)return e(t,i);r=i.default}try{for(const e in i.validator)r=await i.validator[e](r)}catch(r){if(i.retry)return r.message&&i.output.write(r.message+s),e(t,i);throw r}return r}},67841:(e,t,i)=>{"use strict";var s=i(57310).parse,r={ftp:21,gopher:70,http:80,https:443,ws:80,wss:443},n=String.prototype.endsWith||function(e){return e.length<=this.length&&-1!==this.indexOf(e,this.length-e.length)};function a(e){return process.env[e.toLowerCase()]||process.env[e.toUpperCase()]||""}t.getProxyForUrl=function(e){var t="string"==typeof e?s(e):e||{},i=t.protocol,o=t.host,c=t.port;if("string"!=typeof o||!o||"string"!=typeof i)return"";if(i=i.split(":",1)[0],!function(e,t){var i=(a("npm_config_no_proxy")||a("no_proxy")).toLowerCase();return!i||"*"!==i&&i.split(/[,\s]/).every((function(i){if(!i)return!0;var s=i.match(/^(.+):(\d+)$/),r=s?s[1]:i,a=s?parseInt(s[2]):0;return!(!a||a===t)||(/^[.*]/.test(r)?("*"===r.charAt(0)&&(r=r.slice(1)),!n.call(e,r)):e!==r)}))}(o=o.replace(/:\d*$/,""),c=parseInt(c)||r[i]||0))return"";var l=a("npm_config_"+i+"_proxy")||a(i+"_proxy")||a("npm_config_proxy")||a("all_proxy");return l&&-1===l.indexOf("://")&&(l=i+"://"+l),l}},91460:(e,t,i)=>{e.exports=function(e,t){if(e.num)throw new Error("read() no longer accepts a char number limit");if(void 0!==e.default&&"string"!=typeof e.default&&"number"!=typeof e.default)throw new Error("default value must be string or number");var i=e.input||process.stdin,n=e.output||process.stdout,a=(e.prompt||"").trim()+" ",o=e.silent,c=!1,l=e.timeout,p=e.default||"";p&&(o?a+="(